From 7214db14fe37798623acab2e6f5755d05bacbc8f Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 9 Mar 2010 01:24:21 -0500 Subject: wrong param order to strpos() --- plugins/OStatus/classes/HubSub.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/classes/HubSub.php b/plugins/OStatus/classes/HubSub.php index 3120a70f9..c420b3eef 100644 --- a/plugins/OStatus/classes/HubSub.php +++ b/plugins/OStatus/classes/HubSub.php @@ -192,7 +192,7 @@ class HubSub extends Memcached_DataObject // Any existing query string parameters must be preserved $url = $this->callback; - if (strpos('?', $url) !== false) { + if (strpos($url, '?') !== false) { $url .= '&'; } else { $url .= '?'; -- cgit v1.2.3-54-g00ecf From 58192ad68758437a37d8af19d6676d35699ed070 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 9 Mar 2010 10:56:33 -0800 Subject: OStatus: fix exception thrown on HTTP error during feed discovery --- plugins/OStatus/lib/feeddiscovery.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/lib/feeddiscovery.php b/plugins/OStatus/lib/feeddiscovery.php index 7afb71bdc..ff76b229e 100644 --- a/plugins/OStatus/lib/feeddiscovery.php +++ b/plugins/OStatus/lib/feeddiscovery.php @@ -129,7 +129,7 @@ class FeedDiscovery function initFromResponse($response) { if (!$response->isOk()) { - throw new FeedSubBadResponseException($response->getCode()); + throw new FeedSubBadResponseException($response->getStatus()); } $sourceurl = $response->getUrl(); -- cgit v1.2.3-54-g00ecf From 80a17387bfb32ec627a8df55eb902483dba1eb97 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 8 Mar 2010 14:01:43 -0800 Subject: Command input processing now has centralized places for looking up notice, user/profile, and group arguments. OStatus plugin overrides these to allow using webfinger (user@example.com), profile URL (http://example.com/user) and bare profile URL (example.com/user) as arguments. --- lib/command.php | 355 +++++++++++++++++++++----------------- plugins/OStatus/OStatusPlugin.php | 80 +++++++++ 2 files changed, 279 insertions(+), 156 deletions(-) (limited to 'plugins/OStatus') diff --git a/lib/command.php b/lib/command.php index ea7b60372..9d550550f 100644 --- a/lib/command.php +++ b/lib/command.php @@ -1,7 +1,7 @@ user = $user; } - function execute($channel) + /** + * Execute the command and send success or error results + * back via the given communications channel. + * + * @param Channel + */ + public function execute($channel) + { + try { + $this->handle($channel); + } catch (CommandException $e) { + $channel->error($this->user, $e->getMessage()); + } catch (Exception $e) { + common_log(LOG_ERR, "Error handling " . get_class($this) . ": " . $e->getMessage()); + $channel->error($this->user, $e->getMessage()); + } + } + + + /** + * Override this with the meat! + * + * An error to send back to the user may be sent by throwing + * a CommandException with a formatted message. + * + * @param Channel + * @throws CommandException + */ + function handle($channel) { return false; } + + /** + * Look up a notice from an argument, by poster's name to get last post + * or notice_id prefixed with #. + * + * @return Notice + * @throws CommandException + */ + function getNotice($arg) + { + $notice = null; + if (Event::handle('StartCommandGetNotice', array($this, $arg, &$notice))) { + if(substr($this->other,0,1)=='#'){ + // A specific notice_id #123 + + $notice = Notice::staticGet(substr($arg,1)); + if (!$notice) { + throw new CommandException(_('Notice with that id does not exist')); + } + } + + if (Validate::uri($this->other)) { + // A specific notice by URI lookup + $notice = Notice::staticGet('uri', $arg); + } + + if (!$notice) { + // Local or remote profile name to get their last notice. + // May throw an exception and report 'no such user' + $recipient = $this->getProfile($arg); + + $notice = $recipient->getCurrentNotice(); + if (!$notice) { + throw new CommandException(_('User has no last notice')); + } + } + } + Event::handle('EndCommandGetNotice', array($this, $arg, &$notice)); + if (!$notice) { + throw new CommandException(_('Notice with that id does not exist')); + } + return $notice; + } + + /** + * Look up a local or remote profile by nickname. + * + * @return Profile + * @throws CommandException + */ + function getProfile($arg) + { + $profile = null; + if (Event::handle('StartCommandGetProfile', array($this, $arg, &$profile))) { + $profile = + common_relative_profile($this->user, common_canonical_nickname($arg)); + } + Event::handle('EndCommandGetProfile', array($this, $arg, &$profile)); + if (!$profile) { + throw new CommandException(sprintf(_('Could not find a user with nickname %s'), $arg)); + } + return $profile; + } + + /** + * Get a local user by name + * @return User + * @throws CommandException + */ + function getUser($arg) + { + $user = null; + if (Event::handle('StartCommandGetUser', array($this, $arg, &$user))) { + $user = User::staticGet('nickname', $arg); + } + Event::handle('EndCommandGetUser', array($this, $arg, &$user)); + if (!$user){ + throw new CommandException(sprintf(_('Could not find a local user with nickname %s'), + $arg)); + } + return $user; + } + + /** + * Get a local or remote group by name. + * @return User_group + * @throws CommandException + */ + function getGroup($arg) + { + $group = null; + if (Event::handle('StartCommandGetGroup', array($this, $arg, &$group))) { + $group = User_group::getForNickname($arg, $this->user->getProfile()); + } + Event::handle('EndCommandGetGroup', array($this, $arg, &$group)); + if (!$group) { + throw new CommandException(_('No such group.')); + } + return $group; + } +} + +class CommandException extends Exception +{ } class UnimplementedCommand extends Command { - function execute($channel) + function handle($channel) { $channel->error($this->user, _("Sorry, this command is not yet implemented.")); } @@ -81,24 +213,20 @@ class NudgeCommand extends Command parent::__construct($user); $this->other = $other; } - function execute($channel) + + function handle($channel) { - $recipient = User::staticGet('nickname', $this->other); - if(! $recipient){ - $channel->error($this->user, sprintf(_('Could not find a user with nickname %s'), - $this->other)); - }else{ - if ($recipient->id == $this->user->id) { - $channel->error($this->user, _('It does not make a lot of sense to nudge yourself!')); - }else{ - if ($recipient->email && $recipient->emailnotifynudge) { - mail_notify_nudge($this->user, $recipient); - } - // XXX: notify by IM - // XXX: notify by SMS - $channel->output($this->user, sprintf(_('Nudge sent to %s'), - $recipient->nickname)); + $recipient = $this->getUser($this->other); + if ($recipient->id == $this->user->id) { + throw new CommandException(_('It does not make a lot of sense to nudge yourself!')); + } else { + if ($recipient->email && $recipient->emailnotifynudge) { + mail_notify_nudge($this->user, $recipient); } + // XXX: notify by IM + // XXX: notify by SMS + $channel->output($this->user, sprintf(_('Nudge sent to %s'), + $recipient->nickname)); } } } @@ -115,7 +243,7 @@ class InviteCommand extends UnimplementedCommand class StatsCommand extends Command { - function execute($channel) + function handle($channel) { $profile = $this->user->getProfile(); @@ -142,34 +270,9 @@ class FavCommand extends Command $this->other = $other; } - function execute($channel) + function handle($channel) { - if(substr($this->other,0,1)=='#'){ - //favoriting a specific notice_id - - $notice = Notice::staticGet(substr($this->other,1)); - if (!$notice) { - $channel->error($this->user, _('Notice with that id does not exist')); - return; - } - $recipient = $notice->getProfile(); - }else{ - //favoriting a given user's last notice - - $recipient = - common_relative_profile($this->user, common_canonical_nickname($this->other)); - - if (!$recipient) { - $channel->error($this->user, _('No such user.')); - return; - } - $notice = $recipient->getCurrentNotice(); - if (!$notice) { - $channel->error($this->user, _('User has no last notice')); - return; - } - } - + $notice = $this->getNotice($this->other); $fave = Fave::addNew($this->user, $notice); if (!$fave) { @@ -177,7 +280,10 @@ class FavCommand extends Command return; } - $other = User::staticGet('id', $recipient->id); + // @fixme favorite notification should be triggered + // at a lower level + + $other = User::staticGet('id', $notice->profile_id); if ($other && $other->id != $user->id) { if ($other->email && $other->emailnotifyfav) { @@ -191,6 +297,7 @@ class FavCommand extends Command } } + class JoinCommand extends Command { var $other = null; @@ -201,17 +308,10 @@ class JoinCommand extends Command $this->other = $other; } - function execute($channel) + function handle($channel) { - - $nickname = common_canonical_nickname($this->other); - $group = User_group::staticGet('nickname', $nickname); - $cur = $this->user; - - if (!$group) { - $channel->error($cur, _('No such group.')); - return; - } + $group = $this->getGroup($this->other); + $cur = $this->user; if ($cur->isMember($group)) { $channel->error($cur, _('You are already a member of that group')); @@ -249,12 +349,10 @@ class DropCommand extends Command $this->other = $other; } - function execute($channel) + function handle($channel) { - - $nickname = common_canonical_nickname($this->other); - $group = User_group::staticGet('nickname', $nickname); - $cur = $this->user; + $group = $this->getGroup($this->other); + $cur = $this->user; if (!$group) { $channel->error($cur, _('No such group.')); @@ -293,15 +391,9 @@ class WhoisCommand extends Command $this->other = $other; } - function execute($channel) + function handle($channel) { - $recipient = - common_relative_profile($this->user, common_canonical_nickname($this->other)); - - if (!$recipient) { - $channel->error($this->user, _('No such user.')); - return; - } + $recipient = $this->getProfile($this->other); $whois = sprintf(_("%1\$s (%2\$s)"), $recipient->nickname, $recipient->profileurl); @@ -332,9 +424,18 @@ class MessageCommand extends Command $this->text = $text; } - function execute($channel) + function handle($channel) { - $other = User::staticGet('nickname', common_canonical_nickname($this->other)); + try { + $other = $this->getUser($this->other); + } catch (CommandException $e) { + try { + $profile = $this->getProfile($this->other); + } catch (CommandException $f) { + throw $e; + } + throw new CommandException(sprintf(_('%s is a remote profile; you can only send direct messages to users on the same server.'), $this->other)); + } $len = mb_strlen($this->text); @@ -380,33 +481,9 @@ class RepeatCommand extends Command $this->other = $other; } - function execute($channel) + function handle($channel) { - if(substr($this->other,0,1)=='#'){ - //repeating a specific notice_id - - $notice = Notice::staticGet(substr($this->other,1)); - if (!$notice) { - $channel->error($this->user, _('Notice with that id does not exist')); - return; - } - $recipient = $notice->getProfile(); - }else{ - //repeating a given user's last notice - - $recipient = - common_relative_profile($this->user, common_canonical_nickname($this->other)); - - if (!$recipient) { - $channel->error($this->user, _('No such user.')); - return; - } - $notice = $recipient->getCurrentNotice(); - if (!$notice) { - $channel->error($this->user, _('User has no last notice')); - return; - } - } + $notice = $this->getNotice($this->other); if($this->user->id == $notice->profile_id) { @@ -414,7 +491,7 @@ class RepeatCommand extends Command return; } - if ($recipient->hasRepeated($notice->id)) { + if ($this->user->getProfile()->hasRepeated($notice->id)) { $channel->error($this->user, _('Already repeated that notice')); return; } @@ -441,33 +518,10 @@ class ReplyCommand extends Command $this->text = $text; } - function execute($channel) + function handle($channel) { - if(substr($this->other,0,1)=='#'){ - //replying to a specific notice_id - - $notice = Notice::staticGet(substr($this->other,1)); - if (!$notice) { - $channel->error($this->user, _('Notice with that id does not exist')); - return; - } - $recipient = $notice->getProfile(); - }else{ - //replying to a given user's last notice - - $recipient = - common_relative_profile($this->user, common_canonical_nickname($this->other)); - - if (!$recipient) { - $channel->error($this->user, _('No such user.')); - return; - } - $notice = $recipient->getCurrentNotice(); - if (!$notice) { - $channel->error($this->user, _('User has no last notice')); - return; - } - } + $notice = $this->getNotice($this->other); + $recipient = $notice->getProfile(); $len = mb_strlen($this->text); @@ -507,17 +561,10 @@ class GetCommand extends Command $this->other = $other; } - function execute($channel) + function handle($channel) { - $target_nickname = common_canonical_nickname($this->other); - - $target = - common_relative_profile($this->user, $target_nickname); + $target = $this->getProfile($this->other); - if (!$target) { - $channel->error($this->user, _('No such user.')); - return; - } $notice = $target->getCurrentNotice(); if (!$notice) { $channel->error($this->user, _('User has no last notice')); @@ -525,7 +572,7 @@ class GetCommand extends Command } $notice_content = $notice->content; - $channel->output($this->user, $target_nickname . ": " . $notice_content); + $channel->output($this->user, $target->nickname . ": " . $notice_content); } } @@ -540,7 +587,7 @@ class SubCommand extends Command $this->other = $other; } - function execute($channel) + function handle($channel) { if (!$this->other) { @@ -548,16 +595,16 @@ class SubCommand extends Command return; } - $otherUser = User::staticGet('nickname', $this->other); + $target = $this->getProfile($this->other); - if (empty($otherUser)) { - $channel->error($this->user, _('No such user')); - return; + $remote = Remote_profile::staticGet('id', $target->id); + if ($remote) { + throw new CommandException(_("Can't subscribe to OMB profiles by command.")); } try { Subscription::start($this->user->getProfile(), - $otherUser->getProfile()); + $target); $channel->output($this->user, sprintf(_('Subscribed to %s'), $this->other)); } catch (Exception $e) { $channel->error($this->user, $e->getMessage()); @@ -576,22 +623,18 @@ class UnsubCommand extends Command $this->other = $other; } - function execute($channel) + function handle($channel) { if(!$this->other) { $channel->error($this->user, _('Specify the name of the user to unsubscribe from')); return; } - $otherUser = User::staticGet('nickname', $this->other); - - if (empty($otherUser)) { - $channel->error($this->user, _('No such user')); - } + $target = $this->getProfile($this->other); try { Subscription::cancel($this->user->getProfile(), - $otherUser->getProfile()); + $target); $channel->output($this->user, sprintf(_('Unsubscribed from %s'), $this->other)); } catch (Exception $e) { $channel->error($this->user, $e->getMessage()); @@ -607,7 +650,7 @@ class OffCommand extends Command parent::__construct($user); $this->other = $other; } - function execute($channel) + function handle($channel) { if ($other) { $channel->error($this->user, _("Command not yet implemented.")); @@ -630,7 +673,7 @@ class OnCommand extends Command $this->other = $other; } - function execute($channel) + function handle($channel) { if ($other) { $channel->error($this->user, _("Command not yet implemented.")); @@ -646,7 +689,7 @@ class OnCommand extends Command class LoginCommand extends Command { - function execute($channel) + function handle($channel) { $disabled = common_config('logincommand','disabled'); $disabled = isset($disabled) && $disabled; @@ -670,7 +713,7 @@ class LoginCommand extends Command class SubscriptionsCommand extends Command { - function execute($channel) + function handle($channel) { $profile = $this->user->getSubscriptions(0); $nicknames=array(); @@ -692,7 +735,7 @@ class SubscriptionsCommand extends Command class SubscribersCommand extends Command { - function execute($channel) + function handle($channel) { $profile = $this->user->getSubscribers(); $nicknames=array(); @@ -714,7 +757,7 @@ class SubscribersCommand extends Command class GroupsCommand extends Command { - function execute($channel) + function handle($channel) { $group = $this->user->getGroups(); $groups=array(); @@ -735,7 +778,7 @@ class GroupsCommand extends Command class HelpCommand extends Command { - function execute($channel) + function handle($channel) { $channel->output($this->user, _("Commands:\n". diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index bdcaae366..a97f3475b 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -321,6 +321,86 @@ class OStatusPlugin extends Plugin return true; } + /** + * Allow remote profile references to be used in commands: + * sub update@status.net + * whois evan@identi.ca + * reply http://identi.ca/evan hey what's up + * + * @param Command $command + * @param string $arg + * @param Profile &$profile + * @return hook return code + */ + function onStartCommandGetProfile($command, $arg, &$profile) + { + $oprofile = $this->pullRemoteProfile($arg); + if ($oprofile && !$oprofile->isGroup()) { + $profile = $oprofile->localProfile(); + return false; + } else { + return true; + } + } + + /** + * Allow remote group references to be used in commands: + * join group+statusnet@identi.ca + * join http://identi.ca/group/statusnet + * drop identi.ca/group/statusnet + * + * @param Command $command + * @param string $arg + * @param User_group &$group + * @return hook return code + */ + function onStartCommandGetGroup($command, $arg, &$group) + { + $oprofile = $this->pullRemoteProfile($arg); + if ($oprofile && $oprofile->isGroup()) { + $group = $oprofile->localGroup(); + return false; + } else { + return true; + } + } + + protected function pullRemoteProfile($arg) + { + $oprofile = null; + if (preg_match('!^((?:\w+\.)*\w+@(?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+)$!', $arg)) { + // webfinger lookup + try { + return Ostatus_profile::ensureWebfinger($arg); + } catch (Exception $e) { + common_log(LOG_ERR, 'Webfinger lookup failed for ' . + $arg . ': ' . $e->getMessage()); + } + } + + // Look for profile URLs, with or without scheme: + $urls = array(); + if (preg_match('!^https?://((?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+(?:/\w+)+)$!', $arg)) { + $urls[] = $arg; + } + if (preg_match('!^((?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+(?:/\w+)+)$!', $arg)) { + $schemes = array('http', 'https'); + foreach ($schemes as $scheme) { + $urls[] = "$scheme://$arg"; + } + } + + foreach ($urls as $url) { + try { + return Ostatus_profile::ensureProfile($url); + } catch (Exception $e) { + common_log(LOG_ERR, 'Profile lookup failed for ' . + $arg . ': ' . $e->getMessage()); + } + } + return null; + } + /** * Make sure necessary tables are filled out. */ -- cgit v1.2.3-54-g00ecf From 66518df4356ea878bfd8693191f0354caebfb549 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 10 Mar 2010 17:00:05 -0800 Subject: OStatus: reject attempts to create a remote profile for a local user or group. Some stray shadow entries were ending up getting created, which would steal group posts from remote users. Run plugins/OStatus/scripts/fixup-shadow.php for each site to remove any existing ones. --- plugins/OStatus/OStatusPlugin.php | 37 ++++++++++++++++ plugins/OStatus/classes/Ostatus_profile.php | 19 +++++--- plugins/OStatus/scripts/fixup-shadow.php | 69 +++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 plugins/OStatus/scripts/fixup-shadow.php (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index a97f3475b..ef28ab22e 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -929,4 +929,41 @@ class OStatusPlugin extends Plugin return true; } + + /** + * Utility function to check if the given URL is a canonical group profile + * page, and if so return the ID number. + * + * @param string $url + * @return mixed int or false + */ + public static function localGroupFromUrl($url) + { + $template = common_local_url('groupbyid', array('id' => '31337')); + $template = preg_quote($template, '/'); + $template = str_replace('31337', '(\d+)', $template); + if (preg_match("/$template/", $url, $matches)) { + return intval($matches[1]); + } + return false; + } + + /** + * Utility function to check if the given URL is a canonical user profile + * page, and if so return the ID number. + * + * @param string $url + * @return mixed int or false + */ + public static function localProfileFromUrl($url) + { + $template = common_local_url('userbyid', array('id' => '31337')); + $template = preg_quote($template, '/'); + $template = str_replace('31337', '(\d+)', $template); + if (preg_match("/$template/", $url, $matches)) { + return intval($matches[1]); + } + return false; + } + } diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index abc8100ce..6ae8e4fd5 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -675,13 +675,10 @@ class Ostatus_profile extends Memcached_DataObject } // Is the recipient a local group? - // @fixme we need a uri on user_group + // @fixme uri on user_group isn't reliable yet // $group = User_group::staticGet('uri', $recipient); - $template = common_local_url('groupbyid', array('id' => '31337')); - $template = preg_quote($template, '/'); - $template = str_replace('31337', '(\d+)', $template); - if (preg_match("/$template/", $recipient, $matches)) { - $id = $matches[1]; + $id = OStatusPlugin::localGroupFromUrl($recipient); + if ($id) { $group = User_group::staticGet('id', $id); if ($group) { // Deliver to all members of this local group if allowed. @@ -992,7 +989,15 @@ class Ostatus_profile extends Memcached_DataObject if (!$homeuri) { common_log(LOG_DEBUG, __METHOD__ . " empty actor profile URI: " . var_export($activity, true)); - throw new ServerException("No profile URI"); + throw new Exception("No profile URI"); + } + + if (OStatusPlugin::localProfileFromUrl($homeuri)) { + throw new Exception("Local user can't be referenced as remote."); + } + + if (OStatusPlugin::localGroupFromUrl($homeuri)) { + throw new Exception("Local group can't be referenced as remote."); } if (array_key_exists('feedurl', $hints)) { diff --git a/plugins/OStatus/scripts/fixup-shadow.php b/plugins/OStatus/scripts/fixup-shadow.php new file mode 100644 index 000000000..0171b77bc --- /dev/null +++ b/plugins/OStatus/scripts/fixup-shadow.php @@ -0,0 +1,69 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..')); + +$longoptions = array('dry-run'); + +$helptext = << $marker)); +$encProfile = $oprofile->escape($profileTemplate, true); +$encProfile = str_replace($marker, '%', $encProfile); + +$groupTemplate = common_local_url('groupbyid', array('id' => $marker)); +$encGroup = $oprofile->escape($groupTemplate, true); +$encGroup = str_replace($marker, '%', $encGroup); + +$sql = "SELECT * FROM ostatus_profile WHERE uri LIKE '%s' OR uri LIKE '%s'"; +$oprofile->query(sprintf($sql, $encProfile, $encGroup)); + +echo "Found $oprofile->N bogus ostatus_profile entries:\n"; + +while ($oprofile->fetch()) { + echo "$oprofile->uri"; + + if ($dry) { + echo " (unchanged)\n"; + } else { + echo " deleting..."; + $evil = clone($oprofile); + $evil->delete(); + echo " ok\n"; + } +} + +echo "done.\n"; + -- cgit v1.2.3-54-g00ecf From 89582e72262bdba65e6b07699536555d5fa6a497 Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 9 Mar 2010 18:12:37 -0500 Subject: base64_encode/decode -> base64_url_encode/decode --- plugins/OStatus/lib/magicenvelope.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/lib/magicenvelope.php b/plugins/OStatus/lib/magicenvelope.php index fb8c57c71..e8835165c 100644 --- a/plugins/OStatus/lib/magicenvelope.php +++ b/plugins/OStatus/lib/magicenvelope.php @@ -70,7 +70,7 @@ class MagicEnvelope public function signMessage($text, $mimetype, $keypair) { $signature_alg = Magicsig::fromString($keypair); - $armored_text = base64_encode($text); + $armored_text = base64_url_encode($text); return array( 'data' => $armored_text, @@ -108,7 +108,7 @@ class MagicEnvelope public function unfold($env) { $dom = new DOMDocument(); - $dom->loadXML(base64_decode($env['data'])); + $dom->loadXML(base64_url_decode($env['data'])); if ($dom->documentElement->tagName != 'entry') { return false; @@ -165,7 +165,7 @@ class MagicEnvelope return false; } - $text = base64_decode($env['data']); + $text = base64_url_decode($env['data']); $signer_uri = $this->getAuthor($text); try { -- cgit v1.2.3-54-g00ecf From 06612e35e433109e00167ac62d65299210ef0032 Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 9 Mar 2010 18:47:20 -0500 Subject: remove hard-coded me:env check in magicenvelope --- plugins/OStatus/lib/magicenvelope.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/lib/magicenvelope.php b/plugins/OStatus/lib/magicenvelope.php index e8835165c..c927209e4 100644 --- a/plugins/OStatus/lib/magicenvelope.php +++ b/plugins/OStatus/lib/magicenvelope.php @@ -193,11 +193,12 @@ class MagicEnvelope public function fromDom($dom) { - if ($dom->documentElement->tagName == 'entry') { + $env_element = $dom->getElementsByTagNameNS(MagicEnvelope::NS, 'env')->item(0); + if (!$env_element) { $env_element = $dom->getElementsByTagNameNS(MagicEnvelope::NS, 'provenance')->item(0); - } else if ($dom->documentElement->tagName == 'me:env') { - $env_element = $dom->documentElement; - } else { + } + + if (!$env_element) { return false; } -- cgit v1.2.3-54-g00ecf From 512e51105372daf9c85af9284de1463084f03aa9 Mon Sep 17 00:00:00 2001 From: James Walker Date: Thu, 11 Mar 2010 14:32:22 -0500 Subject: fix invalid separator in magic-public-key XRD and matching parsing. --- plugins/OStatus/lib/magicenvelope.php | 6 +++++- plugins/OStatus/lib/xrdaction.php | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/lib/magicenvelope.php b/plugins/OStatus/lib/magicenvelope.php index c927209e4..9266cab5c 100644 --- a/plugins/OStatus/lib/magicenvelope.php +++ b/plugins/OStatus/lib/magicenvelope.php @@ -59,7 +59,11 @@ class MagicEnvelope } if ($xrd->links) { if ($link = Discovery::getService($xrd->links, Magicsig::PUBLICKEYREL)) { - list($type, $keypair) = explode(';', $link['href']); + list($type, $keypair) = explode(',', $link['href']); + if (empty($keypair)) { + // Backwards compatibility check for separator bug in 0.9.0 + list($type, $keypair) = explode(';', $link['href']); + } return $keypair; } } diff --git a/plugins/OStatus/lib/xrdaction.php b/plugins/OStatus/lib/xrdaction.php index 6881292ad..b3c1d8453 100644 --- a/plugins/OStatus/lib/xrdaction.php +++ b/plugins/OStatus/lib/xrdaction.php @@ -91,7 +91,7 @@ class XrdAction extends Action } $xrd->links[] = array('rel' => Magicsig::PUBLICKEYREL, - 'href' => 'data:application/magic-public-key;'. $magickey->toString(false)); + 'href' => 'data:application/magic-public-key,'. $magickey->toString(false)); // TODO - finalize where the redirect should go on the publisher $url = common_local_url('ostatussub') . '?profile={uri}'; -- cgit v1.2.3-54-g00ecf From 9e9ab23e1f936eb62014d8f7b0051f0314ae482c Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 12 Mar 2010 11:19:56 -0800 Subject: Fixes for updating indices, charset/collation and engine type on plugin-created tables. Under MySQL, new tables will be created as InnoDB with UTF-8 (utf8/utf8_bin) same as core tables. Existing plugin tables will have table engine and default charset/collation updated, and string columns will have charset updated, at checkschema time. Switched from 'DESCRIBE' to INFORMATION_SCHEMA for pulling column information in order to get charset. A second hit to INFORMATION_SCHEMA is also needed to get table properties. Indices were only being created at table creation time, which ain't so hot. Now also adding/dropping indices when they change. Fixed up some schema defs in OStatus plugin that were a bit flaky, causing extra alter tables to be run. TODO: Generalize this infrastructure a bit more up to base schema & pg schema classes. --- lib/mysqlschema.php | 236 +++++++++++++++++++++++++++++------ lib/schema.php | 6 + plugins/OStatus/classes/FeedSub.php | 3 +- plugins/OStatus/classes/HubSub.php | 2 +- plugins/OStatus/classes/Magicsig.php | 4 +- 5 files changed, 209 insertions(+), 42 deletions(-) (limited to 'plugins/OStatus') diff --git a/lib/mysqlschema.php b/lib/mysqlschema.php index 485096ac4..455695366 100644 --- a/lib/mysqlschema.php +++ b/lib/mysqlschema.php @@ -90,15 +90,24 @@ class MysqlSchema extends Schema * @param string $name Name of the table to get * * @return TableDef tabledef for that table. + * @throws SchemaTableMissingException */ public function getTableDef($name) { - $res = $this->conn->query('DESCRIBE ' . $name); + $query = "SELECT * FROM INFORMATION_SCHEMA.COLUMNS " . + "WHERE TABLE_SCHEMA='%s' AND TABLE_NAME='%s'"; + $schema = $this->conn->dsn['database']; + $sql = sprintf($query, $schema, $name); + $res = $this->conn->query($sql); if (PEAR::isError($res)) { throw new Exception($res->getMessage()); } + if ($res->numRows() == 0) { + $res->free(); + throw new SchemaTableMissingException("No such table: $name"); + } $td = new TableDef(); @@ -111,9 +120,9 @@ class MysqlSchema extends Schema $cd = new ColumnDef(); - $cd->name = $row['Field']; + $cd->name = $row['COLUMN_NAME']; - $packed = $row['Type']; + $packed = $row['COLUMN_TYPE']; if (preg_match('/^(\w+)\((\d+)\)$/', $packed, $match)) { $cd->type = $match[1]; @@ -122,17 +131,57 @@ class MysqlSchema extends Schema $cd->type = $packed; } - $cd->nullable = ($row['Null'] == 'YES') ? true : false; - $cd->key = $row['Key']; - $cd->default = $row['Default']; - $cd->extra = $row['Extra']; + $cd->nullable = ($row['IS_NULLABLE'] == 'YES') ? true : false; + $cd->key = $row['COLUMN_KEY']; + $cd->default = $row['COLUMN_DEFAULT']; + $cd->extra = $row['EXTRA']; + + // Autoincrement is stuck into the extra column. + // Pull it out so we don't accidentally mod it every time... + $extra = preg_replace('/(^|\s)auto_increment(\s|$)/i', '$1$2', $cd->extra); + if ($extra != $cd->extra) { + $cd->extra = trim($extra); + $cd->auto_increment = true; + } + + // mysql extensions -- not (yet) used by base class + $cd->charset = $row['CHARACTER_SET_NAME']; + $cd->collate = $row['COLLATION_NAME']; $td->columns[] = $cd; } + $res->free(); return $td; } + /** + * Pull the given table properties from INFORMATION_SCHEMA. + * Most of the good stuff is MySQL extensions. + * + * @return array + * @throws Exception if table info can't be looked up + */ + + function getTableProperties($table, $props) + { + $query = "SELECT %s FROM INFORMATION_SCHEMA.TABLES " . + "WHERE TABLE_SCHEMA='%s' AND TABLE_NAME='%s'"; + $schema = $this->conn->dsn['database']; + $sql = sprintf($query, implode(',', $props), $schema, $table); + $res = $this->conn->query($sql); + + $row = array(); + $ok = $res->fetchInto($row, DB_FETCHMODE_ASSOC); + $res->free(); + + if ($ok) { + return $row; + } else { + throw new SchemaTableMissingException("No such table: $table"); + } + } + /** * Gets a ColumnDef object for a single column. * @@ -185,35 +234,26 @@ class MysqlSchema extends Schema } $sql .= $this->_columnSql($cd); - - switch ($cd->key) { - case 'UNI': - $uniques[] = $cd->name; - break; - case 'PRI': - $primary[] = $cd->name; - break; - case 'MUL': - $indices[] = $cd->name; - break; - } } - if (count($primary) > 0) { // it really should be... - $sql .= ",\nconstraint primary key (" . implode(',', $primary) . ")"; + $idx = $this->_indexList($columns); + + if ($idx['primary']) { + $sql .= ",\nconstraint primary key (" . implode(',', $idx['primary']) . ")"; } - foreach ($uniques as $u) { - $sql .= ",\nunique index {$name}_{$u}_idx ($u)"; + foreach ($idx['uniques'] as $u) { + $key = $this->_uniqueKey($name, $u); + $sql .= ",\nunique index $key ($u)"; } - foreach ($indices as $i) { - $sql .= ",\nindex {$name}_{$i}_idx ($i)"; + foreach ($idx['indices'] as $i) { + $key = $this->_key($name, $i); + $sql .= ",\nindex $key ($i)"; } - $sql .= "); "; + $sql .= ") ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; "; - common_log(LOG_INFO, $sql); $res = $this->conn->query($sql); if (PEAR::isError($res)) { @@ -223,6 +263,47 @@ class MysqlSchema extends Schema return true; } + /** + * Look over a list of column definitions and list up which + * indices will be present + */ + private function _indexList(array $columns) + { + $list = array('uniques' => array(), + 'primary' => array(), + 'indices' => array()); + foreach ($columns as $cd) { + switch ($cd->key) { + case 'UNI': + $list['uniques'][] = $cd->name; + break; + case 'PRI': + $list['primary'][] = $cd->name; + break; + case 'MUL': + $list['indices'][] = $cd->name; + break; + } + } + return $list; + } + + /** + * Get the unique index key name for a given column on this table + */ + function _uniqueKey($tableName, $columnName) + { + return $this->_key($tableName, $columnName); + } + + /** + * Get the index key name for a given column on this table + */ + function _key($tableName, $columnName) + { + return "{$tableName}_{$columnName}_idx"; + } + /** * Drops a table from the schema * @@ -394,21 +475,20 @@ class MysqlSchema extends Schema try { $td = $this->getTableDef($tableName); - } catch (Exception $e) { - if (preg_match('/no such table/', $e->getMessage())) { - return $this->createTable($tableName, $columns); - } else { - throw $e; - } + } catch (SchemaTableMissingException $e) { + return $this->createTable($tableName, $columns); } $cur = $this->_names($td->columns); $new = $this->_names($columns); - $toadd = array_diff($new, $cur); - $todrop = array_diff($cur, $new); - $same = array_intersect($new, $cur); - $tomod = array(); + $dropIndex = array(); + $toadd = array_diff($new, $cur); + $todrop = array_diff($cur, $new); + $same = array_intersect($new, $cur); + $tomod = array(); + $addIndex = array(); + $tableProps = array(); foreach ($same as $m) { $curCol = $this->_byName($td->columns, $m); @@ -416,10 +496,64 @@ class MysqlSchema extends Schema if (!$newCol->equals($curCol)) { $tomod[] = $newCol->name; + continue; + } + + // Earlier versions may have accidentally left tables at default + // charsets which might be latin1 or other freakish things. + if ($this->_isString($curCol)) { + if ($curCol->charset != 'utf8') { + $tomod[] = $newCol->name; + continue; + } + } + } + + // Find any indices we have to change... + $curIdx = $this->_indexList($td->columns); + $newIdx = $this->_indexList($columns); + + if ($curIdx['primary'] != $newIdx['primary']) { + if ($curIdx['primary']) { + $dropIndex[] = 'drop primary key'; + } + if ($newIdx['primary']) { + $keys = implode(',', $newIdx['primary']); + $addIndex[] = "add constraint primary key ($keys)"; } } - if (count($toadd) + count($todrop) + count($tomod) == 0) { + $dropUnique = array_diff($curIdx['uniques'], $newIdx['uniques']); + $addUnique = array_diff($newIdx['uniques'], $curIdx['uniques']); + foreach ($dropUnique as $columnName) { + $dropIndex[] = 'drop key ' . $this->_uniqueKey($tableName, $columnName); + } + foreach ($addUnique as $columnName) { + $addIndex[] = 'add constraint unique key ' . $this->_uniqueKey($tableName, $columnName) . " ($columnName)";; + } + + $dropMultiple = array_diff($curIdx['indices'], $newIdx['indices']); + $addMultiple = array_diff($newIdx['indices'], $curIdx['indices']); + foreach ($dropMultiple as $columnName) { + $dropIndex[] = 'drop key ' . $this->_key($tableName, $columnName); + } + foreach ($addMultiple as $columnName) { + $addIndex[] = 'add key ' . $this->_key($tableName, $columnName) . " ($columnName)"; + } + + // Check for table properties: make sure we're using a sane + // engine type and charset/collation. + // @fixme make the default engine configurable? + $oldProps = $this->getTableProperties($tableName, array('ENGINE', 'TABLE_COLLATION')); + if (strtolower($oldProps['ENGINE']) != 'innodb') { + $tableProps['ENGINE'] = 'InnoDB'; + } + if (strtolower($oldProps['TABLE_COLLATION']) != 'utf8_bin') { + $tableProps['DEFAULT CHARSET'] = 'utf8'; + $tableProps['COLLATE'] = 'utf8_bin'; + } + + if (count($dropIndex) + count($toadd) + count($todrop) + count($tomod) + count($addIndex) + count($tableProps) == 0) { // nothing to do return true; } @@ -429,6 +563,10 @@ class MysqlSchema extends Schema $phrase = array(); + foreach ($dropIndex as $indexSql) { + $phrase[] = $indexSql; + } + foreach ($toadd as $columnName) { $cd = $this->_byName($columns, $columnName); @@ -445,8 +583,17 @@ class MysqlSchema extends Schema $phrase[] = 'MODIFY COLUMN ' . $this->_columnSql($cd); } + foreach ($addIndex as $indexSql) { + $phrase[] = $indexSql; + } + + foreach ($tableProps as $key => $val) { + $phrase[] = "$key=$val"; + } + $sql = 'ALTER TABLE ' . $tableName . ' ' . implode(', ', $phrase); + common_log(LOG_DEBUG, __METHOD__ . ': ' . $sql); $res = $this->conn->query($sql); if (PEAR::isError($res)) { @@ -519,6 +666,10 @@ class MysqlSchema extends Schema $sql .= "{$cd->type} "; } + if ($this->_isString($cd)) { + $sql .= " CHARACTER SET utf8 "; + } + if (!empty($cd->default)) { $sql .= "default {$cd->default} "; } else { @@ -535,4 +686,13 @@ class MysqlSchema extends Schema return $sql; } + + /** + * Is this column a string type? + */ + private function _isString(ColumnDef $cd) + { + $strings = array('char', 'varchar', 'text'); + return in_array(strtolower($cd->type), $strings); + } } diff --git a/lib/schema.php b/lib/schema.php index 137b814e0..1503c96d4 100644 --- a/lib/schema.php +++ b/lib/schema.php @@ -485,3 +485,9 @@ class Schema return $sql; } } + +class SchemaTableMissingException extends Exception +{ + // no-op +} + diff --git a/plugins/OStatus/classes/FeedSub.php b/plugins/OStatus/classes/FeedSub.php index b848b6b1d..80ba37bc1 100644 --- a/plugins/OStatus/classes/FeedSub.php +++ b/plugins/OStatus/classes/FeedSub.php @@ -110,7 +110,7 @@ class FeedSub extends Memcached_DataObject /*size*/ null, /*nullable*/ false, /*key*/ 'PRI', - /*default*/ '0', + /*default*/ null, /*extra*/ null, /*auto_increment*/ true), new ColumnDef('uri', 'varchar', @@ -450,3 +450,4 @@ class FeedSub extends Memcached_DataObject } } + diff --git a/plugins/OStatus/classes/HubSub.php b/plugins/OStatus/classes/HubSub.php index c420b3eef..cdace3c1f 100644 --- a/plugins/OStatus/classes/HubSub.php +++ b/plugins/OStatus/classes/HubSub.php @@ -77,7 +77,7 @@ class HubSub extends Memcached_DataObject new ColumnDef('topic', 'varchar', /*size*/255, /*nullable*/false, - /*key*/'KEY'), + /*key*/'MUL'), new ColumnDef('callback', 'varchar', 255, false), new ColumnDef('secret', 'text', diff --git a/plugins/OStatus/classes/Magicsig.php b/plugins/OStatus/classes/Magicsig.php index 5a46aeeb6..b0a411e5d 100644 --- a/plugins/OStatus/classes/Magicsig.php +++ b/plugins/OStatus/classes/Magicsig.php @@ -70,7 +70,7 @@ class Magicsig extends Memcached_DataObject static function schemaDef() { return array(new ColumnDef('user_id', 'integer', - null, true, 'PRI'), + null, false, 'PRI'), new ColumnDef('keypair', 'varchar', 255, false), new ColumnDef('alg', 'varchar', @@ -230,4 +230,4 @@ function base64_url_encode($input) function base64_url_decode($input) { return base64_decode(strtr($input, '-_', '+/')); -} \ No newline at end of file +} -- cgit v1.2.3-54-g00ecf From 114f046691282ef96a7e5795082f97bd9c41ab97 Mon Sep 17 00:00:00 2001 From: James Walker Date: Fri, 12 Mar 2010 18:50:00 -0500 Subject: removing deprecated PEAR Crypt_RSA --- plugins/OStatus/extlib/Crypt/RSA.php | 524 -------------- plugins/OStatus/extlib/Crypt/RSA/ErrorHandler.php | 234 ------- plugins/OStatus/extlib/Crypt/RSA/Key.php | 315 --------- plugins/OStatus/extlib/Crypt/RSA/KeyPair.php | 804 ---------------------- plugins/OStatus/extlib/Crypt/RSA/Math/BCMath.php | 482 ------------- plugins/OStatus/extlib/Crypt/RSA/Math/BigInt.php | 313 --------- plugins/OStatus/extlib/Crypt/RSA/Math/GMP.php | 361 ---------- plugins/OStatus/extlib/Crypt/RSA/MathLoader.php | 135 ---- 8 files changed, 3168 deletions(-) delete mode 100644 plugins/OStatus/extlib/Crypt/RSA.php delete mode 100644 plugins/OStatus/extlib/Crypt/RSA/ErrorHandler.php delete mode 100644 plugins/OStatus/extlib/Crypt/RSA/Key.php delete mode 100644 plugins/OStatus/extlib/Crypt/RSA/KeyPair.php delete mode 100644 plugins/OStatus/extlib/Crypt/RSA/Math/BCMath.php delete mode 100644 plugins/OStatus/extlib/Crypt/RSA/Math/BigInt.php delete mode 100644 plugins/OStatus/extlib/Crypt/RSA/Math/GMP.php delete mode 100644 plugins/OStatus/extlib/Crypt/RSA/MathLoader.php (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/extlib/Crypt/RSA.php b/plugins/OStatus/extlib/Crypt/RSA.php deleted file mode 100644 index 16dfa54d4..000000000 --- a/plugins/OStatus/extlib/Crypt/RSA.php +++ /dev/null @@ -1,524 +0,0 @@ - - * @copyright 2005, 2006 Alexander Valyalkin - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version 1.2.0b - * @link http://pear.php.net/package/Crypt_RSA - */ - -/** - * RSA error handling facilities - */ -require_once 'Crypt/RSA/ErrorHandler.php'; - -/** - * loader for math wrappers - */ -require_once 'Crypt/RSA/MathLoader.php'; - -/** - * helper class for mange single key - */ -require_once 'Crypt/RSA/Key.php'; - -/** - * helper class for manage key pair - */ -require_once 'Crypt/RSA/KeyPair.php'; - -/** - * Crypt_RSA class, derived from Crypt_RSA_ErrorHandler - * - * Provides the following functions: - * - setParams($params) - sets parameters of current object - * - encrypt($plain_data, $key = null) - encrypts data - * - decrypt($enc_data, $key = null) - decrypts data - * - createSign($doc, $private_key = null) - signs document by private key - * - validateSign($doc, $signature, $public_key = null) - validates signature of document - * - * Example usage: - * // creating an error handler - * $error_handler = create_function('$obj', 'echo "error: ", $obj->getMessage(), "\n"'); - * - * // 1024-bit key pair generation - * $key_pair = new Crypt_RSA_KeyPair(1024); - * - * // check consistence of Crypt_RSA_KeyPair object - * $error_handler($key_pair); - * - * // creating Crypt_RSA object - * $rsa_obj = new Crypt_RSA; - * - * // check consistence of Crypt_RSA object - * $error_handler($rsa_obj); - * - * // set error handler on Crypt_RSA object ( see Crypt/RSA/ErrorHandler.php for details ) - * $rsa_obj->setErrorHandler($error_handler); - * - * // encryption (usually using public key) - * $enc_data = $rsa_obj->encrypt($plain_data, $key_pair->getPublicKey()); - * - * // decryption (usually using private key) - * $plain_data = $rsa_obj->decrypt($enc_data, $key_pair->getPrivateKey()); - * - * // signing - * $signature = $rsa_obj->createSign($document, $key_pair->getPrivateKey()); - * - * // signature checking - * $is_valid = $rsa_obj->validateSign($document, $signature, $key_pair->getPublicKey()); - * - * // signing many documents by one private key - * $rsa_obj = new Crypt_RSA(array('private_key' => $key_pair->getPrivateKey())); - * // check consistence of Crypt_RSA object - * $error_handler($rsa_obj); - * // set error handler ( see Crypt/RSA/ErrorHandler.php for details ) - * $rsa_obj->setErrorHandler($error_handler); - * // sign many documents - * $sign_1 = $rsa_obj->sign($doc_1); - * $sign_2 = $rsa_obj->sign($doc_2); - * //... - * $sign_n = $rsa_obj->sign($doc_n); - * - * // changing default hash function, which is used for sign - * // creating/validation - * $rsa_obj->setParams(array('hash_func' => 'md5')); - * - * // using factory() method instead of constructor (it returns PEAR_Error object on failure) - * $rsa_obj = &Crypt_RSA::factory(); - * if (PEAR::isError($rsa_obj)) { - * echo "error: ", $rsa_obj->getMessage(), "\n"; - * } - * - * @category Encryption - * @package Crypt_RSA - * @author Alexander Valyalkin - * @copyright 2005, 2006 Alexander Valyalkin - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @link http://pear.php.net/package/Crypt_RSA - * @version @package_version@ - * @access public - */ -class Crypt_RSA extends Crypt_RSA_ErrorHandler -{ - /** - * Reference to math wrapper, which is used to - * manipulate large integers in RSA algorithm. - * - * @var object of Crypt_RSA_Math_* class - * @access private - */ - var $_math_obj; - - /** - * key for encryption, which is used by encrypt() method - * - * @var object of Crypt_RSA_KEY class - * @access private - */ - var $_enc_key; - - /** - * key for decryption, which is used by decrypt() method - * - * @var object of Crypt_RSA_KEY class - * @access private - */ - var $_dec_key; - - /** - * public key, which is used by validateSign() method - * - * @var object of Crypt_RSA_KEY class - * @access private - */ - var $_public_key; - - /** - * private key, which is used by createSign() method - * - * @var object of Crypt_RSA_KEY class - * @access private - */ - var $_private_key; - - /** - * name of hash function, which is used by validateSign() - * and createSign() methods. Default hash function is SHA-1 - * - * @var string - * @access private - */ - var $_hash_func = 'sha1'; - - /** - * Crypt_RSA constructor. - * - * @param array $params - * Optional associative array of parameters, such as: - * enc_key, dec_key, private_key, public_key, hash_func. - * See setParams() method for more detailed description of - * these parameters. - * @param string $wrapper_name - * Name of math wrapper, which will be used to - * perform different operations with big integers. - * See contents of Crypt/RSA/Math folder for examples of wrappers. - * Read docs/Crypt_RSA/docs/math_wrappers.txt for details. - * @param string $error_handler name of error handler function - * - * @access public - */ - function Crypt_RSA($params = null, $wrapper_name = 'default', $error_handler = '') - { - // set error handler - $this->setErrorHandler($error_handler); - // try to load math wrapper - $obj = &Crypt_RSA_MathLoader::loadWrapper($wrapper_name); - if ($this->isError($obj)) { - // error during loading of math wrapper - // Crypt_RSA object is partially constructed. - $this->pushError($obj); - return; - } - $this->_math_obj = &$obj; - - if (!is_null($params)) { - if (!$this->setParams($params)) { - // error in Crypt_RSA::setParams() function - return; - } - } - } - - /** - * Crypt_RSA factory. - * - * @param array $params - * Optional associative array of parameters, such as: - * enc_key, dec_key, private_key, public_key, hash_func. - * See setParams() method for more detailed description of - * these parameters. - * @param string $wrapper_name - * Name of math wrapper, which will be used to - * perform different operations with big integers. - * See contents of Crypt/RSA/Math folder for examples of wrappers. - * Read docs/Crypt_RSA/docs/math_wrappers.txt for details. - * @param string $error_handler name of error handler function - * - * @return object new Crypt_RSA object on success or PEAR_Error object on failure - * @access public - */ - function &factory($params = null, $wrapper_name = 'default', $error_handler = '') - { - $obj = &new Crypt_RSA($params, $wrapper_name, $error_handler); - if ($obj->isError()) { - // error during creating a new object. Retrurn PEAR_Error object - return $obj->getLastError(); - } - // object created successfully. Return it - return $obj; - } - - /** - * Accepts any combination of available parameters as associative array: - * enc_key - encryption key for encrypt() method - * dec_key - decryption key for decrypt() method - * public_key - key for validateSign() method - * private_key - key for createSign() method - * hash_func - name of hash function, which will be used to create and validate sign - * - * @param array $params - * associative array of permitted parameters (see above) - * - * @return bool true on success or false on error - * @access public - */ - function setParams($params) - { - if (!is_array($params)) { - $this->pushError('parameters must be passed to function as associative array', CRYPT_RSA_ERROR_WRONG_PARAMS); - return false; - } - - if (isset($params['enc_key'])) { - if (Crypt_RSA_Key::isValid($params['enc_key'])) { - $this->_enc_key = $params['enc_key']; - } - else { - $this->pushError('wrong encryption key. It must be an object of Crypt_RSA_Key class', CRYPT_RSA_ERROR_WRONG_KEY); - return false; - } - } - if (isset($params['dec_key'])) { - if (Crypt_RSA_Key::isValid($params['dec_key'])) { - $this->_dec_key = $params['dec_key']; - } - else { - $this->pushError('wrong decryption key. It must be an object of Crypt_RSA_Key class', CRYPT_RSA_ERROR_WRONG_KEY); - return false; - } - } - if (isset($params['private_key'])) { - if (Crypt_RSA_Key::isValid($params['private_key'])) { - if ($params['private_key']->getKeyType() != 'private') { - $this->pushError('private key must have "private" attribute', CRYPT_RSA_ERROR_WRONG_KEY_TYPE); - return false; - } - $this->_private_key = $params['private_key']; - } - else { - $this->pushError('wrong private key. It must be an object of Crypt_RSA_Key class', CRYPT_RSA_ERROR_WRONG_KEY); - return false; - } - } - if (isset($params['public_key'])) { - if (Crypt_RSA_Key::isValid($params['public_key'])) { - if ($params['public_key']->getKeyType() != 'public') { - $this->pushError('public key must have "public" attribute', CRYPT_RSA_ERROR_WRONG_KEY_TYPE); - return false; - } - $this->_public_key = $params['public_key']; - } - else { - $this->pushError('wrong public key. It must be an object of Crypt_RSA_Key class', CRYPT_RSA_ERROR_WRONG_KEY); - return false; - } - } - if (isset($params['hash_func'])) { - if (!function_exists($params['hash_func'])) { - $this->pushError('cannot find hash function with name [' . $params['hash_func'] . ']', CRYPT_RSA_ERROR_WRONG_HASH_FUNC); - return false; - } - $this->_hash_func = $params['hash_func']; - } - return true; // all ok - } - - /** - * Ecnrypts $plain_data by the key $this->_enc_key or $key. - * - * @param string $plain_data data, which must be encrypted - * @param object $key encryption key (object of Crypt_RSA_Key class) - * @return mixed - * encrypted data as string on success or false on error - * - * @access public - */ - function encrypt($plain_data, $key = null) - { - $enc_data = $this->encryptBinary($plain_data, $key); - if ($enc_data !== false) { - return base64_encode($enc_data); - } - // error during encripting data - return false; - } - - /** - * Ecnrypts $plain_data by the key $this->_enc_key or $key. - * - * @param string $plain_data data, which must be encrypted - * @param object $key encryption key (object of Crypt_RSA_Key class) - * @return mixed - * encrypted data as binary string on success or false on error - * - * @access public - */ - function encryptBinary($plain_data, $key = null) - { - if (is_null($key)) { - // use current encryption key - $key = $this->_enc_key; - } - else if (!Crypt_RSA_Key::isValid($key)) { - $this->pushError('invalid encryption key. It must be an object of Crypt_RSA_Key class', CRYPT_RSA_ERROR_WRONG_KEY); - return false; - } - - // append tail \x01 to plain data. It needs for correctly decrypting of data - $plain_data .= "\x01"; - - $plain_data = $this->_math_obj->bin2int($plain_data); - $exp = $this->_math_obj->bin2int($key->getExponent()); - $modulus = $this->_math_obj->bin2int($key->getModulus()); - - // divide plain data into chunks - $data_len = $this->_math_obj->bitLen($plain_data); - $chunk_len = $key->getKeyLength() - 1; - $block_len = (int) ceil($chunk_len / 8); - $curr_pos = 0; - $enc_data = ''; - while ($curr_pos < $data_len) { - $tmp = $this->_math_obj->subint($plain_data, $curr_pos, $chunk_len); - $enc_data .= str_pad( - $this->_math_obj->int2bin($this->_math_obj->powmod($tmp, $exp, $modulus)), - $block_len, - "\0" - ); - $curr_pos += $chunk_len; - } - return $enc_data; - } - - /** - * Decrypts $enc_data by the key $this->_dec_key or $key. - * - * @param string $enc_data encrypted data as string - * @param object $key decryption key (object of RSA_Crypt_Key class) - * @return mixed - * decrypted data as string on success or false on error - * - * @access public - */ - function decrypt($enc_data, $key = null) - { - $enc_data = base64_decode($enc_data); - return $this->decryptBinary($enc_data, $key); - } - - /** - * Decrypts $enc_data by the key $this->_dec_key or $key. - * - * @param string $enc_data encrypted data as binary string - * @param object $key decryption key (object of RSA_Crypt_Key class) - * @return mixed - * decrypted data as string on success or false on error - * - * @access public - */ - function decryptBinary($enc_data, $key = null) - { - if (is_null($key)) { - // use current decryption key - $key = $this->_dec_key; - } - else if (!Crypt_RSA_Key::isValid($key)) { - $this->pushError('invalid decryption key. It must be an object of Crypt_RSA_Key class', CRYPT_RSA_ERROR_WRONG_KEY); - return false; - } - - $exp = $this->_math_obj->bin2int($key->getExponent()); - $modulus = $this->_math_obj->bin2int($key->getModulus()); - - $data_len = strlen($enc_data); - $chunk_len = $key->getKeyLength() - 1; - $block_len = (int) ceil($chunk_len / 8); - $curr_pos = 0; - $bit_pos = 0; - $plain_data = $this->_math_obj->bin2int("\0"); - while ($curr_pos < $data_len) { - $tmp = $this->_math_obj->bin2int(substr($enc_data, $curr_pos, $block_len)); - $tmp = $this->_math_obj->powmod($tmp, $exp, $modulus); - $plain_data = $this->_math_obj->bitOr($plain_data, $tmp, $bit_pos); - $bit_pos += $chunk_len; - $curr_pos += $block_len; - } - $result = $this->_math_obj->int2bin($plain_data); - - // delete tail, containing of \x01 - $tail = ord($result{strlen($result) - 1}); - if ($tail != 1) { - $this->pushError("Error tail of decrypted text = {$tail}. Expected 1", CRYPT_RSA_ERROR_WRONG_TAIL); - return false; - } - return substr($result, 0, -1); - } - - /** - * Creates sign for document $document, using $this->_private_key or $private_key - * as private key and $this->_hash_func or $hash_func as hash function. - * - * @param string $document document, which must be signed - * @param object $private_key private key (object of Crypt_RSA_Key type) - * @param string $hash_func name of hash function, which will be used during signing - * @return mixed - * signature of $document as string on success or false on error - * - * @access public - */ - function createSign($document, $private_key = null, $hash_func = null) - { - // check private key - if (is_null($private_key)) { - $private_key = $this->_private_key; - } - else if (!Crypt_RSA_Key::isValid($private_key)) { - $this->pushError('invalid private key. It must be an object of Crypt_RSA_Key class', CRYPT_RSA_ERROR_WRONG_KEY); - return false; - } - if ($private_key->getKeyType() != 'private') { - $this->pushError('signing key must be private', CRYPT_RSA_ERROR_NEED_PRV_KEY); - return false; - } - - // check hash_func - if (is_null($hash_func)) { - $hash_func = $this->_hash_func; - } - if (!function_exists($hash_func)) { - $this->pushError("cannot find hash function with name [$hash_func]", CRYPT_RSA_ERROR_WRONG_HASH_FUNC); - return false; - } - - return $this->encrypt($hash_func($document), $private_key); - } - - /** - * Validates $signature for document $document with public key $this->_public_key - * or $public_key and hash function $this->_hash_func or $hash_func. - * - * @param string $document document, signature of which must be validated - * @param string $signature signature, which must be validated - * @param object $public_key public key (object of Crypt_RSA_Key class) - * @param string $hash_func hash function, which will be used during validating signature - * @return mixed - * true, if signature of document is valid - * false, if signature of document is invalid - * null on error - * - * @access public - */ - function validateSign($document, $signature, $public_key = null, $hash_func = null) - { - // check public key - if (is_null($public_key)) { - $public_key = $this->_public_key; - } - else if (!Crypt_RSA_Key::isValid($public_key)) { - $this->pushError('invalid public key. It must be an object of Crypt_RSA_Key class', CRYPT_RSA_ERROR_WRONG_KEY); - return null; - } - if ($public_key->getKeyType() != 'public') { - $this->pushError('validating key must be public', CRYPT_RSA_ERROR_NEED_PUB_KEY); - return null; - } - - // check hash_func - if (is_null($hash_func)) { - $hash_func = $this->_hash_func; - } - if (!function_exists($hash_func)) { - $this->pushError("cannot find hash function with name [$hash_func]", CRYPT_RSA_ERROR_WRONG_HASH_FUNC); - return null; - } - - return $hash_func($document) == $this->decrypt($signature, $public_key); - } -} - -?> \ No newline at end of file diff --git a/plugins/OStatus/extlib/Crypt/RSA/ErrorHandler.php b/plugins/OStatus/extlib/Crypt/RSA/ErrorHandler.php deleted file mode 100644 index 8f39741e0..000000000 --- a/plugins/OStatus/extlib/Crypt/RSA/ErrorHandler.php +++ /dev/null @@ -1,234 +0,0 @@ - - * @copyright 2005 Alexander Valyalkin - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version CVS: $Id: ErrorHandler.php,v 1.4 2009/01/05 08:30:29 clockwerx Exp $ - * @link http://pear.php.net/package/Crypt_RSA - */ - -/** - * uses PEAR's error handling - */ -require_once 'PEAR.php'; - -/** - * cannot load required extension for math wrapper - */ -define('CRYPT_RSA_ERROR_NO_EXT', 1); - -/** - * cannot load any math wrappers. - * Possible reasons: - * - there is no any wrappers (they must exist in Crypt/RSA/Math folder ) - * - all available wrappers are incorrect (read docs/Crypt_RSA/docs/math_wrappers.txt ) - * - cannot load any extension, required by available wrappers - */ -define('CRYPT_RSA_ERROR_NO_WRAPPERS', 2); - -/** - * cannot find file, containing requested math wrapper - */ -define('CRYPT_RSA_ERROR_NO_FILE', 3); - -/** - * cannot find math wrapper class in the math wrapper file - */ -define('CRYPT_RSA_ERROR_NO_CLASS', 4); - -/** - * invalid key type passed to function (it must be 'public' or 'private') - */ -define('CRYPT_RSA_ERROR_WRONG_KEY_TYPE', 5); - -/** - * key modulus must be greater than key exponent - */ -define('CRYPT_RSA_ERROR_EXP_GE_MOD', 6); - -/** - * missing $key_len parameter in Crypt_RSA_KeyPair::generate($key_len) function - */ -define('CRYPT_RSA_ERROR_MISSING_KEY_LEN', 7); - -/** - * wrong key object passed to function (it must be an object of Crypt_RSA_Key class) - */ -define('CRYPT_RSA_ERROR_WRONG_KEY', 8); - -/** - * wrong name of hash function passed to Crypt_RSA::setParams() function - */ -define('CRYPT_RSA_ERROR_WRONG_HASH_FUNC', 9); - -/** - * key, used for signing, must be private - */ -define('CRYPT_RSA_ERROR_NEED_PRV_KEY', 10); - -/** - * key, used for sign validating, must be public - */ -define('CRYPT_RSA_ERROR_NEED_PUB_KEY', 11); - -/** - * parameters must be passed to function as associative array - */ -define('CRYPT_RSA_ERROR_WRONG_PARAMS', 12); - -/** - * error tail of decrypted text. Maybe, wrong decryption key? - */ -define('CRYPT_RSA_ERROR_WRONG_TAIL', 13); - -/** - * Crypt_RSA_ErrorHandler class. - * - * This class is used as base for Crypt_RSA, Crypt_RSA_Key - * and Crypt_RSA_KeyPair classes. - * - * It provides following functions: - * - isError() - returns true, if list contains errors, else returns false - * - getErrorList() - returns error list - * - getLastError() - returns last error from error list or false, if list is empty - * - pushError($errstr) - pushes $errstr into the error list - * - setErrorHandler($new_error_handler) - sets error handler function - * - getErrorHandler() - returns name of error handler function - * - * @category Encryption - * @package Crypt_RSA - * @author Alexander Valyalkin - * @copyright 2005 Alexander Valyalkin - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version Release: @package_version@ - * @link http://pear.php.net/package/Crypt_RSA - * @access public - */ -class Crypt_RSA_ErrorHandler -{ - /** - * array of error objects, pushed by $this->pushError() - * - * @var array - * @access private - */ - var $_errors = array(); - - /** - * name of error handler - function, which calls on $this->pushError() call - * - * @var string - * @access private - */ - var $_error_handler = ''; - - /** - * Returns true if list of errors is not empty, else returns false - * - * @param mixed $err Check if the object is an error - * - * @return bool true, if list of errors is not empty or $err is PEAR_Error object, else false - * @access public - */ - function isError($err = null) - { - return is_null($err) ? (sizeof($this->_errors) > 0) : PEAR::isError($err); - } - - /** - * Returns list of all errors, pushed to error list by $this->pushError() - * - * @return array list of errors (usually it contains objects of PEAR_Error class) - * @access public - */ - function getErrorList() - { - return $this->_errors; - } - - /** - * Returns last error from errors list or false, if list is empty - * - * @return mixed - * last error from errors list (usually it is PEAR_Error object) - * or false, if list is empty. - * - * @access public - */ - function getLastError() - { - $len = sizeof($this->_errors); - return $len ? $this->_errors[$len - 1] : false; - } - - /** - * pushes error object $error to the error list - * - * @param string $errstr error string - * @param int $errno error number - * - * @return bool true on success, false on error - * @access public - */ - function pushError($errstr, $errno = 0) - { - $this->_errors[] = PEAR::raiseError($errstr, $errno); - - if ($this->_error_handler != '') { - // call user defined error handler - $func = $this->_error_handler; - $func($this); - } - return true; - } - - /** - * sets error handler to function with name $func_name. - * Function $func_name must accept one parameter - current - * object, which triggered error. - * - * @param string $func_name name of error handler function - * - * @return bool true on success, false on error - * @access public - */ - function setErrorHandler($func_name = '') - { - if ($func_name == '') { - $this->_error_handler = ''; - } - if (!function_exists($func_name)) { - return false; - } - $this->_error_handler = $func_name; - return true; - } - - /** - * returns name of current error handler, or null if there is no error handler - * - * @return mixed error handler name as string or null, if there is no error handler - * @access public - */ - function getErrorHandler() - { - return $this->_error_handler; - } -} - -?> diff --git a/plugins/OStatus/extlib/Crypt/RSA/Key.php b/plugins/OStatus/extlib/Crypt/RSA/Key.php deleted file mode 100644 index 659530229..000000000 --- a/plugins/OStatus/extlib/Crypt/RSA/Key.php +++ /dev/null @@ -1,315 +0,0 @@ - - * @copyright 2005 Alexander Valyalkin - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version CVS: $Id: Key.php,v 1.6 2009/01/05 08:30:29 clockwerx Exp $ - * @link http://pear.php.net/package/Crypt_RSA - */ - -/** - * RSA error handling facilities - */ -require_once 'Crypt/RSA/ErrorHandler.php'; - -/** - * loader for RSA math wrappers - */ -require_once 'Crypt/RSA/MathLoader.php'; - -/** - * Crypt_RSA_Key class, derived from Crypt_RSA_ErrorHandler - * - * Provides the following functions: - * - getKeyLength() - returns bit key length - * - getExponent() - returns key exponent as binary string - * - getModulus() - returns key modulus as binary string - * - getKeyType() - returns type of the key (public or private) - * - toString() - returns serialized key as string - * - fromString($key_str) - static function; returns key, unserialized from string - * - isValid($key) - static function for validating of $key - * - * Example usage: - * // create new 1024-bit key pair - * $key_pair = new Crypt_RSA_KeyPair(1024); - * - * // get public key (its class is Crypt_RSA_Key) - * $key = $key_pair->getPublicKey(); - * - * // get key length - * $len = $key->getKeyLength(); - * - * // get modulus as string - * $modulus = $key->getModulus(); - * - * // get exponent as string - * $exponent = $key->getExponent(); - * - * // get string represenation of key (use it instead of serialization of Crypt_RSA_Key object) - * $key_in_str = $key->toString(); - * - * // restore key object from string using 'BigInt' math wrapper - * $key = Crypt_RSA_Key::fromString($key_in_str, 'BigInt'); - * - * // error check - * if ($key->isError()) { - * echo "error while unserializing key object:\n"; - * $erorr = $key->getLastError(); - * echo $error->getMessage(), "\n"; - * } - * - * // validate key - * if (Crypt_RSA_Key::isValid($key)) echo 'valid key'; - * else echo 'invalid key'; - * - * // using factory() method instead of constructor (it returns PEAR_Error object on failure) - * $rsa_obj = &Crypt_RSA_Key::factory($modulus, $exp, $key_type); - * if (PEAR::isError($rsa_obj)) { - * echo "error: ", $rsa_obj->getMessage(), "\n"; - * } - * - * @category Encryption - * @package Crypt_RSA - * @author Alexander Valyalkin - * @copyright 2005 Alexander Valyalkin - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version Release: @package_version@ - * @link http://pear.php.net/package/Crypt_RSA - * @access public - */ -class Crypt_RSA_Key extends Crypt_RSA_ErrorHandler -{ - /** - * Reference to math wrapper object, which is used to - * manipulate large integers in RSA algorithm. - * - * @var object of Crypt_RSA_Math_* class - * @access private - */ - var $_math_obj; - - /** - * shared modulus - * - * @var string - * @access private - */ - var $_modulus; - - /** - * exponent - * - * @var string - * @access private - */ - var $_exp; - - /** - * key type (private or public) - * - * @var string - * @access private - */ - var $_key_type; - - /** - * key length in bits - * - * @var int - * @access private - */ - var $_key_len; - - /** - * Crypt_RSA_Key constructor. - * - * You should pass in the name of math wrapper, which will be used to - * perform different operations with big integers. - * See contents of Crypt/RSA/Math folder for examples of wrappers. - * Read docs/Crypt_RSA/docs/math_wrappers.txt for details. - * - * @param string $modulus key modulus - * @param string $exp key exponent - * @param string $key_type type of the key (public or private) - * @param string $wrapper_name wrapper to use - * @param string $error_handler name of error handler function - * - * @access public - */ - function Crypt_RSA_Key($modulus, $exp, $key_type, $wrapper_name = 'default', $error_handler = '') - { - // set error handler - $this->setErrorHandler($error_handler); - // try to load math wrapper $wrapper_name - $obj = &Crypt_RSA_MathLoader::loadWrapper($wrapper_name); - if ($this->isError($obj)) { - // error during loading of math wrapper - $this->pushError($obj); // push error object into error list - return; - } - $this->_math_obj = &$obj; - - $this->_modulus = $modulus; - $this->_exp = $exp; - - if (!in_array($key_type, array('private', 'public'))) { - $this->pushError('invalid key type. It must be private or public', CRYPT_RSA_ERROR_WRONG_KEY_TYPE); - return; - } - $this->_key_type = $key_type; - - /* check length of modulus & exponent ( abs(modulus) > abs(exp) ) */ - $mod_num = $this->_math_obj->bin2int($this->_modulus); - $exp_num = $this->_math_obj->bin2int($this->_exp); - - if ($this->_math_obj->cmpAbs($mod_num, $exp_num) <= 0) { - $this->pushError('modulus must be greater than exponent', CRYPT_RSA_ERROR_EXP_GE_MOD); - return; - } - - // determine key length - $this->_key_len = $this->_math_obj->bitLen($mod_num); - } - - /** - * Crypt_RSA_Key factory. - * - * @param string $modulus key modulus - * @param string $exp key exponent - * @param string $key_type type of the key (public or private) - * @param string $wrapper_name wrapper to use - * @param string $error_handler name of error handler function - * - * @return object new Crypt_RSA_Key object on success or PEAR_Error object on failure - * @access public - */ - function factory($modulus, $exp, $key_type, $wrapper_name = 'default', $error_handler = '') - { - $obj = new Crypt_RSA_Key($modulus, $exp, $key_type, $wrapper_name, $error_handler); - if ($obj->isError()) { - // error during creating a new object. Retrurn PEAR_Error object - return $obj->getLastError(); - } - // object created successfully. Return it - return $obj; - } - - /** - * Calculates bit length of the key - * - * @return int bit length of key - * @access public - */ - function getKeyLength() - { - return $this->_key_len; - } - - /** - * Returns modulus part of the key as binary string, - * which can be used to construct new Crypt_RSA_Key object. - * - * @return string modulus as binary string - * @access public - */ - function getModulus() - { - return $this->_modulus; - } - - /** - * Returns exponent part of the key as binary string, - * which can be used to construct new Crypt_RSA_Key object. - * - * @return string exponent as binary string - * @access public - */ - function getExponent() - { - return $this->_exp; - } - - /** - * Returns key type (public, private) - * - * @return string key type (public, private) - * @access public - */ - function getKeyType() - { - return $this->_key_type; - } - - /** - * Returns string representation of key - * - * @return string key, serialized to string - * @access public - */ - function toString() - { - return base64_encode( - serialize( - array($this->_modulus, $this->_exp, $this->_key_type) - ) - ); - } - - /** - * Returns Crypt_RSA_Key object, unserialized from - * string representation of key. - * - * optional parameter $wrapper_name - is the name of math wrapper, - * which will be used during unserialization of this object. - * - * This function can be called statically: - * $key = Crypt_RSA_Key::fromString($key_in_string, 'BigInt'); - * - * @param string $key_str RSA key, serialized into string - * @param string $wrapper_name optional math wrapper name - * - * @return object key as Crypt_RSA_Key object - * @access public - * @static - */ - function fromString($key_str, $wrapper_name = 'default') - { - list($modulus, $exponent, $key_type) = unserialize(base64_decode($key_str)); - $obj = new Crypt_RSA_Key($modulus, $exponent, $key_type, $wrapper_name); - return $obj; - } - - /** - * Validates key - * This function can be called statically: - * $is_valid = Crypt_RSA_Key::isValid($key) - * - * Returns true, if $key is valid Crypt_RSA key, else returns false - * - * @param object $key Crypt_RSA_Key object for validating - * - * @return bool true if $key is valid, else false - * @access public - */ - function isValid($key) - { - return (is_object($key) && strtolower(get_class($key)) === strtolower(__CLASS__)); - } -} - -?> diff --git a/plugins/OStatus/extlib/Crypt/RSA/KeyPair.php b/plugins/OStatus/extlib/Crypt/RSA/KeyPair.php deleted file mode 100644 index ecc0b7dc7..000000000 --- a/plugins/OStatus/extlib/Crypt/RSA/KeyPair.php +++ /dev/null @@ -1,804 +0,0 @@ - - * @copyright 2005 Alexander Valyalkin - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version CVS: $Id: KeyPair.php,v 1.7 2009/01/05 08:30:29 clockwerx Exp $ - * @link http://pear.php.net/package/Crypt_RSA - */ - -/** - * RSA error handling facilities - */ -require_once 'Crypt/RSA/ErrorHandler.php'; - -/** - * loader for RSA math wrappers - */ -require_once 'Crypt/RSA/MathLoader.php'; - -/** - * helper class for single key managing - */ -require_once 'Crypt/RSA/Key.php'; - -/** - * Crypt_RSA_KeyPair class, derived from Crypt_RSA_ErrorHandler - * - * Provides the following functions: - * - generate($key) - generates new key pair - * - getPublicKey() - returns public key - * - getPrivateKey() - returns private key - * - getKeyLength() - returns bit key length - * - setRandomGenerator($func_name) - sets random generator to $func_name - * - fromPEMString($str) - retrieves keypair from PEM-encoded string - * - toPEMString() - stores keypair to PEM-encoded string - * - isEqual($keypair2) - compares current keypair to $keypair2 - * - * Example usage: - * // create new 1024-bit key pair - * $key_pair = new Crypt_RSA_KeyPair(1024); - * - * // error check - * if ($key_pair->isError()) { - * echo "error while initializing Crypt_RSA_KeyPair object:\n"; - * $erorr = $key_pair->getLastError(); - * echo $error->getMessage(), "\n"; - * } - * - * // get public key - * $public_key = $key_pair->getPublicKey(); - * - * // get private key - * $private_key = $key_pair->getPrivateKey(); - * - * // generate new 512-bit key pair - * $key_pair->generate(512); - * - * // error check - * if ($key_pair->isError()) { - * echo "error while generating key pair:\n"; - * $erorr = $key_pair->getLastError(); - * echo $error->getMessage(), "\n"; - * } - * - * // get key pair length - * $length = $key_pair->getKeyLength(); - * - * // set random generator to $func_name, where $func_name - * // consists name of random generator function. See comments - * // before setRandomGenerator() method for details - * $key_pair->setRandomGenerator($func_name); - * - * // error check - * if ($key_pair->isError()) { - * echo "error while changing random generator:\n"; - * $erorr = $key_pair->getLastError(); - * echo $error->getMessage(), "\n"; - * } - * - * // using factory() method instead of constructor (it returns PEAR_Error object on failure) - * $rsa_obj = &Crypt_RSA_KeyPair::factory($key_len); - * if (PEAR::isError($rsa_obj)) { - * echo "error: ", $rsa_obj->getMessage(), "\n"; - * } - * - * // read key pair from PEM-encoded string: - * $str = "-----BEGIN RSA PRIVATE KEY-----" - * . "MCsCAQACBHr5LDkCAwEAAQIEBc6jbQIDAOCfAgMAjCcCAk3pAgJMawIDAL41" - * . "-----END RSA PRIVATE KEY-----"; - * $keypair = Crypt_RSA_KeyPair::fromPEMString($str); - * - * // read key pair from .pem file 'private.pem': - * $str = file_get_contents('private.pem'); - * $keypair = Crypt_RSA_KeyPair::fromPEMString($str); - * - * // generate and write 1024-bit key pair to .pem file 'private_new.pem' - * $keypair = new Crypt_RSA_KeyPair(1024); - * $str = $keypair->toPEMString(); - * file_put_contents('private_new.pem', $str); - * - * // compare $keypair1 to $keypair2 - * if ($keypair1->isEqual($keypair2)) { - * echo "keypair1 = keypair2\n"; - * } - * else { - * echo "keypair1 != keypair2\n"; - * } - * - * @category Encryption - * @package Crypt_RSA - * @author Alexander Valyalkin - * @copyright 2005 Alexander Valyalkin - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version Release: @package_version@ - * @link http://pear.php.net/package/Crypt_RSA - * @access public - */ -class Crypt_RSA_KeyPair extends Crypt_RSA_ErrorHandler -{ - /** - * Reference to math wrapper object, which is used to - * manipulate large integers in RSA algorithm. - * - * @var object of Crypt_RSA_Math_* class - * @access private - */ - var $_math_obj; - - /** - * length of each key in the key pair - * - * @var int - * @access private - */ - var $_key_len; - - /** - * public key - * - * @var object of Crypt_RSA_KEY class - * @access private - */ - var $_public_key; - - /** - * private key - * - * @var object of Crypt_RSA_KEY class - * @access private - */ - var $_private_key; - - /** - * name of function, which is used as random generator - * - * @var string - * @access private - */ - var $_random_generator; - - /** - * RSA keypair attributes [version, n, e, d, p, q, dmp1, dmq1, iqmp] as associative array - * - * @var array - * @access private - */ - var $_attrs; - - /** - * Returns names of keypair attributes from $this->_attrs array - * - * @return array Array of keypair attributes names - * @access private - */ - function _get_attr_names() - { - return array('version', 'n', 'e', 'd', 'p', 'q', 'dmp1', 'dmq1', 'iqmp'); - } - - /** - * Parses ASN.1 string [$str] starting form position [$pos]. - * Returns tag and string value of parsed object. - * - * @param string $str - * @param int &$pos - * @param Crypt_RSA_ErrorHandler &$err_handler - * - * @return mixed Array('tag' => ..., 'str' => ...) on success, false on error - * @access private - */ - function _ASN1Parse($str, &$pos, &$err_handler) - { - $max_pos = strlen($str); - if ($max_pos < 2) { - $err_handler->pushError("ASN.1 string too short"); - return false; - } - - // get ASN.1 tag value - $tag = ord($str[$pos++]) & 0x1f; - if ($tag == 0x1f) { - $tag = 0; - do { - $n = ord($str[$pos++]); - $tag <<= 7; - $tag |= $n & 0x7f; - } while (($n & 0x80) && $pos < $max_pos); - } - if ($pos >= $max_pos) { - $err_handler->pushError("ASN.1 string too short"); - return false; - } - - // get ASN.1 object length - $len = ord($str[$pos++]); - if ($len & 0x80) { - $n = $len & 0x1f; - $len = 0; - while ($n-- && $pos < $max_pos) { - $len <<= 8; - $len |= ord($str[$pos++]); - } - } - if ($pos >= $max_pos || $len > $max_pos - $pos) { - $err_handler->pushError("ASN.1 string too short"); - return false; - } - - // get string value of ASN.1 object - $str = substr($str, $pos, $len); - - return array( - 'tag' => $tag, - 'str' => $str, - ); - } - - /** - * Parses ASN.1 sting [$str] starting from position [$pos]. - * Returns string representation of number, which can be passed - * in bin2int() function of math wrapper. - * - * @param string $str - * @param int &$pos - * @param Crypt_RSA_ErrorHandler &$err_handler - * - * @return mixed string representation of parsed number on success, false on error - * @access private - */ - function _ASN1ParseInt($str, &$pos, &$err_handler) - { - $tmp = Crypt_RSA_KeyPair::_ASN1Parse($str, $pos, $err_handler); - if ($err_handler->isError()) { - return false; - } - if ($tmp['tag'] != 0x02) { - $errstr = sprintf("wrong ASN tag value: 0x%02x. Expected 0x02 (INTEGER)", $tmp['tag']); - $err_handler->pushError($errstr); - return false; - } - $pos += strlen($tmp['str']); - - return strrev($tmp['str']); - } - - /** - * Constructs ASN.1 string from tag $tag and object $str - * - * @param string $str ASN.1 object string - * @param int $tag ASN.1 tag value - * @param bool $is_constructed - * @param bool $is_private - * - * @return ASN.1-encoded string - * @access private - */ - function _ASN1Store($str, $tag, $is_constructed = false, $is_private = false) - { - $out = ''; - - // encode ASN.1 tag value - $tag_ext = ($is_constructed ? 0x20 : 0) | ($is_private ? 0xc0 : 0); - if ($tag < 0x1f) { - $out .= chr($tag | $tag_ext); - } else { - $out .= chr($tag_ext | 0x1f); - $tmp = chr($tag & 0x7f); - $tag >>= 7; - while ($tag) { - $tmp .= chr(($tag & 0x7f) | 0x80); - $tag >>= 7; - } - $out .= strrev($tmp); - } - - // encode ASN.1 object length - $len = strlen($str); - if ($len < 0x7f) { - $out .= chr($len); - } else { - $tmp = ''; - $n = 0; - while ($len) { - $tmp .= chr($len & 0xff); - $len >>= 8; - $n++; - } - $out .= chr($n | 0x80); - $out .= strrev($tmp); - } - - return $out . $str; - } - - /** - * Constructs ASN.1 string from binary representation of big integer - * - * @param string $str binary representation of big integer - * - * @return ASN.1-encoded string - * @access private - */ - function _ASN1StoreInt($str) - { - $str = strrev($str); - return Crypt_RSA_KeyPair::_ASN1Store($str, 0x02); - } - - /** - * Crypt_RSA_KeyPair constructor. - * - * Wrapper: name of math wrapper, which will be used to - * perform different operations with big integers. - * See contents of Crypt/RSA/Math folder for examples of wrappers. - * Read docs/Crypt_RSA/docs/math_wrappers.txt for details. - * - * @param int $key_len bit length of key pair, which will be generated in constructor - * @param string $wrapper_name wrapper name - * @param string $error_handler name of error handler function - * @param callback $random_generator function which will be used as random generator - * - * @access public - */ - function Crypt_RSA_KeyPair($key_len, $wrapper_name = 'default', $error_handler = '', $random_generator = null) - { - // set error handler - $this->setErrorHandler($error_handler); - // try to load math wrapper - $obj = &Crypt_RSA_MathLoader::loadWrapper($wrapper_name); - if ($this->isError($obj)) { - // error during loading of math wrapper - $this->pushError($obj); - return; - } - $this->_math_obj = &$obj; - - // set random generator - if (!$this->setRandomGenerator($random_generator)) { - // error in setRandomGenerator() function - return; - } - - if (is_array($key_len)) { - // ugly BC hack - it is possible to pass RSA private key attributes [version, n, e, d, p, q, dmp1, dmq1, iqmp] - // as associative array instead of key length to Crypt_RSA_KeyPair constructor - $rsa_attrs = $key_len; - - // convert attributes to big integers - $attr_names = $this->_get_attr_names(); - foreach ($attr_names as $attr) { - if (!isset($rsa_attrs[$attr])) { - $this->pushError("missing required RSA attribute [$attr]"); - return; - } - ${$attr} = $this->_math_obj->bin2int($rsa_attrs[$attr]); - } - - // check primality of p and q - if (!$this->_math_obj->isPrime($p)) { - $this->pushError("[p] must be prime"); - return; - } - if (!$this->_math_obj->isPrime($q)) { - $this->pushError("[q] must be prime"); - return; - } - - // check n = p * q - $n1 = $this->_math_obj->mul($p, $q); - if ($this->_math_obj->cmpAbs($n, $n1)) { - $this->pushError("n != p * q"); - return; - } - - // check e * d = 1 mod (p-1) * (q-1) - $p1 = $this->_math_obj->dec($p); - $q1 = $this->_math_obj->dec($q); - $p1q1 = $this->_math_obj->mul($p1, $q1); - $ed = $this->_math_obj->mul($e, $d); - $one = $this->_math_obj->mod($ed, $p1q1); - if (!$this->_math_obj->isOne($one)) { - $this->pushError("e * d != 1 mod (p-1)*(q-1)"); - return; - } - - // check dmp1 = d mod (p-1) - $dmp = $this->_math_obj->mod($d, $p1); - if ($this->_math_obj->cmpAbs($dmp, $dmp1)) { - $this->pushError("dmp1 != d mod (p-1)"); - return; - } - - // check dmq1 = d mod (q-1) - $dmq = $this->_math_obj->mod($d, $q1); - if ($this->_math_obj->cmpAbs($dmq, $dmq1)) { - $this->pushError("dmq1 != d mod (q-1)"); - return; - } - - // check iqmp = 1/q mod p - $q1 = $this->_math_obj->invmod($iqmp, $p); - if ($this->_math_obj->cmpAbs($q, $q1)) { - $this->pushError("iqmp != 1/q mod p"); - return; - } - - // try to create public key object - $public_key = &new Crypt_RSA_Key($rsa_attrs['n'], $rsa_attrs['e'], 'public', $wrapper_name, $error_handler); - if ($public_key->isError()) { - // error during creating public object - $this->pushError($public_key->getLastError()); - return; - } - - // try to create private key object - $private_key = &new Crypt_RSA_Key($rsa_attrs['n'], $rsa_attrs['d'], 'private', $wrapper_name, $error_handler); - if ($private_key->isError()) { - // error during creating private key object - $this->pushError($private_key->getLastError()); - return; - } - - $this->_public_key = $public_key; - $this->_private_key = $private_key; - $this->_key_len = $public_key->getKeyLength(); - $this->_attrs = $rsa_attrs; - } else { - // generate key pair - if (!$this->generate($key_len)) { - // error during generating key pair - return; - } - } - } - - /** - * Crypt_RSA_KeyPair factory. - * - * Wrapper - Name of math wrapper, which will be used to - * perform different operations with big integers. - * See contents of Crypt/RSA/Math folder for examples of wrappers. - * Read docs/Crypt_RSA/docs/math_wrappers.txt for details. - * - * @param int $key_len bit length of key pair, which will be generated in constructor - * @param string $wrapper_name wrapper name - * @param string $error_handler name of error handler function - * @param callback $random_generator function which will be used as random generator - * - * @return object new Crypt_RSA_KeyPair object on success or PEAR_Error object on failure - * @access public - */ - function &factory($key_len, $wrapper_name = 'default', $error_handler = '', $random_generator = null) - { - $obj = &new Crypt_RSA_KeyPair($key_len, $wrapper_name, $error_handler, $random_generator); - if ($obj->isError()) { - // error during creating a new object. Return PEAR_Error object - return $obj->getLastError(); - } - // object created successfully. Return it - return $obj; - } - - /** - * Generates new Crypt_RSA key pair with length $key_len. - * If $key_len is missed, use an old key length from $this->_key_len - * - * @param int $key_len bit length of key pair, which will be generated - * - * @return bool true on success or false on error - * @access public - */ - function generate($key_len = null) - { - if (is_null($key_len)) { - // use an old key length - $key_len = $this->_key_len; - if (is_null($key_len)) { - $this->pushError('missing key_len parameter', CRYPT_RSA_ERROR_MISSING_KEY_LEN); - return false; - } - } - - // minimal key length is 8 bit ;) - if ($key_len < 8) { - $key_len = 8; - } - // store key length in the _key_len property - $this->_key_len = $key_len; - - // set [e] to 0x10001 (65537) - $e = $this->_math_obj->bin2int("\x01\x00\x01"); - - // generate [p], [q] and [n] - $p_len = intval(($key_len + 1) / 2); - $q_len = $key_len - $p_len; - $p1 = $q1 = 0; - do { - // generate prime number [$p] with length [$p_len] with the following condition: - // GCD($e, $p - 1) = 1 - do { - $p = $this->_math_obj->getPrime($p_len, $this->_random_generator); - $p1 = $this->_math_obj->dec($p); - $tmp = $this->_math_obj->GCD($e, $p1); - } while (!$this->_math_obj->isOne($tmp)); - // generate prime number [$q] with length [$q_len] with the following conditions: - // GCD($e, $q - 1) = 1 - // $q != $p - do { - $q = $this->_math_obj->getPrime($q_len, $this->_random_generator); - $q1 = $this->_math_obj->dec($q); - $tmp = $this->_math_obj->GCD($e, $q1); - } while (!$this->_math_obj->isOne($tmp) && !$this->_math_obj->cmpAbs($q, $p)); - // if (p < q), then exchange them - if ($this->_math_obj->cmpAbs($p, $q) < 0) { - $tmp = $p; - $p = $q; - $q = $tmp; - $tmp = $p1; - $p1 = $q1; - $q1 = $tmp; - } - // calculate n = p * q - $n = $this->_math_obj->mul($p, $q); - } while ($this->_math_obj->bitLen($n) != $key_len); - - // calculate d = 1/e mod (p - 1) * (q - 1) - $pq = $this->_math_obj->mul($p1, $q1); - $d = $this->_math_obj->invmod($e, $pq); - - // calculate dmp1 = d mod (p - 1) - $dmp1 = $this->_math_obj->mod($d, $p1); - - // calculate dmq1 = d mod (q - 1) - $dmq1 = $this->_math_obj->mod($d, $q1); - - // calculate iqmp = 1/q mod p - $iqmp = $this->_math_obj->invmod($q, $p); - - // store RSA keypair attributes - $this->_attrs = array( - 'version' => "\x00", - 'n' => $this->_math_obj->int2bin($n), - 'e' => $this->_math_obj->int2bin($e), - 'd' => $this->_math_obj->int2bin($d), - 'p' => $this->_math_obj->int2bin($p), - 'q' => $this->_math_obj->int2bin($q), - 'dmp1' => $this->_math_obj->int2bin($dmp1), - 'dmq1' => $this->_math_obj->int2bin($dmq1), - 'iqmp' => $this->_math_obj->int2bin($iqmp), - ); - - $n = $this->_attrs['n']; - $e = $this->_attrs['e']; - $d = $this->_attrs['d']; - - // try to create public key object - $obj = &new Crypt_RSA_Key($n, $e, 'public', $this->_math_obj->getWrapperName(), $this->_error_handler); - if ($obj->isError()) { - // error during creating public object - $this->pushError($obj->getLastError()); - return false; - } - $this->_public_key = &$obj; - - // try to create private key object - $obj = &new Crypt_RSA_Key($n, $d, 'private', $this->_math_obj->getWrapperName(), $this->_error_handler); - if ($obj->isError()) { - // error during creating private key object - $this->pushError($obj->getLastError()); - return false; - } - $this->_private_key = &$obj; - - return true; // key pair successfully generated - } - - /** - * Returns public key from the pair - * - * @return object public key object of class Crypt_RSA_Key - * @access public - */ - function getPublicKey() - { - return $this->_public_key; - } - - /** - * Returns private key from the pair - * - * @return object private key object of class Crypt_RSA_Key - * @access public - */ - function getPrivateKey() - { - return $this->_private_key; - } - - /** - * Sets name of random generator function for key generation. - * If parameter is skipped, then sets to default random generator. - * - * Random generator function must return integer with at least 8 lower - * significant bits, which will be used as random values. - * - * @param string $random_generator name of random generator function - * - * @return bool true on success or false on error - * @access public - */ - function setRandomGenerator($random_generator = null) - { - static $default_random_generator = null; - - if (is_string($random_generator)) { - // set user's random generator - if (!function_exists($random_generator)) { - $this->pushError("can't find random generator function with name [{$random_generator}]"); - return false; - } - $this->_random_generator = $random_generator; - } else { - // set default random generator - $this->_random_generator = is_null($default_random_generator) ? - ($default_random_generator = create_function('', '$a=explode(" ",microtime());return(int)($a[0]*1000000);')) : - $default_random_generator; - } - return true; - } - - /** - * Returns length of each key in the key pair - * - * @return int bit length of each key in key pair - * @access public - */ - function getKeyLength() - { - return $this->_key_len; - } - - /** - * Retrieves RSA keypair from PEM-encoded string, containing RSA private key. - * Example of such string: - * -----BEGIN RSA PRIVATE KEY----- - * MCsCAQACBHtvbSECAwEAAQIEeYrk3QIDAOF3AgMAjCcCAmdnAgJMawIDALEk - * -----END RSA PRIVATE KEY----- - * - * Wrapper: Name of math wrapper, which will be used to - * perform different operations with big integers. - * See contents of Crypt/RSA/Math folder for examples of wrappers. - * Read docs/Crypt_RSA/docs/math_wrappers.txt for details. - * - * @param string $str PEM-encoded string - * @param string $wrapper_name Wrapper name - * @param string $error_handler name of error handler function - * - * @return Crypt_RSA_KeyPair object on success, PEAR_Error object on error - * @access public - * @static - */ - function &fromPEMString($str, $wrapper_name = 'default', $error_handler = '') - { - if (isset($this)) { - if ($wrapper_name == 'default') { - $wrapper_name = $this->_math_obj->getWrapperName(); - } - if ($error_handler == '') { - $error_handler = $this->_error_handler; - } - } - $err_handler = &new Crypt_RSA_ErrorHandler; - $err_handler->setErrorHandler($error_handler); - - // search for base64-encoded private key - if (!preg_match('/-----BEGIN RSA PRIVATE KEY-----([^-]+)-----END RSA PRIVATE KEY-----/', $str, $matches)) { - $err_handler->pushError("can't find RSA private key in the string [{$str}]"); - return $err_handler->getLastError(); - } - - // parse private key. It is ASN.1-encoded - $str = base64_decode($matches[1]); - $pos = 0; - $tmp = Crypt_RSA_KeyPair::_ASN1Parse($str, $pos, $err_handler); - if ($err_handler->isError()) { - return $err_handler->getLastError(); - } - if ($tmp['tag'] != 0x10) { - $errstr = sprintf("wrong ASN tag value: 0x%02x. Expected 0x10 (SEQUENCE)", $tmp['tag']); - $err_handler->pushError($errstr); - return $err_handler->getLastError(); - } - - // parse ASN.1 SEQUENCE for RSA private key - $attr_names = Crypt_RSA_KeyPair::_get_attr_names(); - $n = sizeof($attr_names); - $rsa_attrs = array(); - for ($i = 0; $i < $n; $i++) { - $tmp = Crypt_RSA_KeyPair::_ASN1ParseInt($str, $pos, $err_handler); - if ($err_handler->isError()) { - return $err_handler->getLastError(); - } - $attr = $attr_names[$i]; - $rsa_attrs[$attr] = $tmp; - } - - // create Crypt_RSA_KeyPair object. - $keypair = &new Crypt_RSA_KeyPair($rsa_attrs, $wrapper_name, $error_handler); - if ($keypair->isError()) { - return $keypair->getLastError(); - } - - return $keypair; - } - - /** - * converts keypair to PEM-encoded string, which can be stroed in - * .pem compatible files, contianing RSA private key. - * - * @return string PEM-encoded keypair on success, false on error - * @access public - */ - function toPEMString() - { - // store RSA private key attributes into ASN.1 string - $str = ''; - $attr_names = $this->_get_attr_names(); - $n = sizeof($attr_names); - $rsa_attrs = $this->_attrs; - for ($i = 0; $i < $n; $i++) { - $attr = $attr_names[$i]; - if (!isset($rsa_attrs[$attr])) { - $this->pushError("Cannot find value for ASN.1 attribute [$attr]"); - return false; - } - $tmp = $rsa_attrs[$attr]; - $str .= Crypt_RSA_KeyPair::_ASN1StoreInt($tmp); - } - - // prepend $str by ASN.1 SEQUENCE (0x10) header - $str = Crypt_RSA_KeyPair::_ASN1Store($str, 0x10, true); - - // encode and format PEM string - $str = base64_encode($str); - $str = chunk_split($str, 64, "\n"); - return "-----BEGIN RSA PRIVATE KEY-----\n$str-----END RSA PRIVATE KEY-----\n"; - } - - /** - * Compares keypairs in Crypt_RSA_KeyPair objects $this and $key_pair - * - * @param Crypt_RSA_KeyPair $key_pair keypair to compare - * - * @return bool true, if keypair stored in $this equal to keypair stored in $key_pair - * @access public - */ - function isEqual($key_pair) - { - $attr_names = $this->_get_attr_names(); - foreach ($attr_names as $attr) { - if ($this->_attrs[$attr] != $key_pair->_attrs[$attr]) { - return false; - } - } - return true; - } -} - -?> diff --git a/plugins/OStatus/extlib/Crypt/RSA/Math/BCMath.php b/plugins/OStatus/extlib/Crypt/RSA/Math/BCMath.php deleted file mode 100644 index 646ff6710..000000000 --- a/plugins/OStatus/extlib/Crypt/RSA/Math/BCMath.php +++ /dev/null @@ -1,482 +0,0 @@ - - * @copyright 2006 Alexander Valyalkin - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version 1.2.0b - * @link http://pear.php.net/package/Crypt_RSA - */ - -/** - * Crypt_RSA_Math_BCMath class. - * - * Provides set of math functions, which are used by Crypt_RSA package - * This class is a wrapper for PHP BCMath extension. - * See http://php.net/manual/en/ref.bc.php for details. - * - * @category Encryption - * @package Crypt_RSA - * @author Alexander Valyalkin - * @copyright 2005, 2006 Alexander Valyalkin - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @link http://pear.php.net/package/Crypt_RSA - * @version @package_version@ - * @access public - */ -class Crypt_RSA_Math_BCMath -{ - /** - * error description - * - * @var string - * @access public - */ - var $errstr = ''; - - /** - * Performs Miller-Rabin primality test for number $num - * with base $base. Returns true, if $num is strong pseudoprime - * by base $base. Else returns false. - * - * @param string $num - * @param string $base - * @return bool - * @access private - */ - function _millerTest($num, $base) - { - if (!bccomp($num, '1')) { - // 1 is not prime ;) - return false; - } - $tmp = bcsub($num, '1'); - - $zero_bits = 0; - while (!bccomp(bcmod($tmp, '2'), '0')) { - $zero_bits++; - $tmp = bcdiv($tmp, '2'); - } - - $tmp = $this->powmod($base, $tmp, $num); - if (!bccomp($tmp, '1')) { - // $num is probably prime - return true; - } - - while ($zero_bits--) { - if (!bccomp(bcadd($tmp, '1'), $num)) { - // $num is probably prime - return true; - } - $tmp = $this->powmod($tmp, '2', $num); - } - // $num is composite - return false; - } - - /** - * Crypt_RSA_Math_BCMath constructor. - * Checks an existance of PHP BCMath extension. - * On failure saves error description in $this->errstr - * - * @access public - */ - function Crypt_RSA_Math_BCMath() - { - if (!extension_loaded('bcmath')) { - if (!@dl('bcmath.' . PHP_SHLIB_SUFFIX) && !@dl('php_bcmath.' . PHP_SHLIB_SUFFIX)) { - // cannot load BCMath extension. Set error string - $this->errstr = 'Crypt_RSA package requires the BCMath extension. See http://php.net/manual/en/ref.bc.php for details'; - return; - } - } - } - - /** - * Transforms binary representation of large integer into its native form. - * - * Example of transformation: - * $str = "\x12\x34\x56\x78\x90"; - * $num = 0x9078563412; - * - * @param string $str - * @return string - * @access public - */ - function bin2int($str) - { - $result = '0'; - $n = strlen($str); - do { - $result = bcadd(bcmul($result, '256'), ord($str{--$n})); - } while ($n > 0); - return $result; - } - - /** - * Transforms large integer into binary representation. - * - * Example of transformation: - * $num = 0x9078563412; - * $str = "\x12\x34\x56\x78\x90"; - * - * @param string $num - * @return string - * @access public - */ - function int2bin($num) - { - $result = ''; - do { - $result .= chr(bcmod($num, '256')); - $num = bcdiv($num, '256'); - } while (bccomp($num, '0')); - return $result; - } - - /** - * Calculates pow($num, $pow) (mod $mod) - * - * @param string $num - * @param string $pow - * @param string $mod - * @return string - * @access public - */ - function powmod($num, $pow, $mod) - { - if (function_exists('bcpowmod')) { - // bcpowmod is only available under PHP5 - return bcpowmod($num, $pow, $mod); - } - - // emulate bcpowmod - $result = '1'; - do { - if (!bccomp(bcmod($pow, '2'), '1')) { - $result = bcmod(bcmul($result, $num), $mod); - } - $num = bcmod(bcpow($num, '2'), $mod); - $pow = bcdiv($pow, '2'); - } while (bccomp($pow, '0')); - return $result; - } - - /** - * Calculates $num1 * $num2 - * - * @param string $num1 - * @param string $num2 - * @return string - * @access public - */ - function mul($num1, $num2) - { - return bcmul($num1, $num2); - } - - /** - * Calculates $num1 % $num2 - * - * @param string $num1 - * @param string $num2 - * @return string - * @access public - */ - function mod($num1, $num2) - { - return bcmod($num1, $num2); - } - - /** - * Compares abs($num1) to abs($num2). - * Returns: - * -1, if abs($num1) < abs($num2) - * 0, if abs($num1) == abs($num2) - * 1, if abs($num1) > abs($num2) - * - * @param string $num1 - * @param string $num2 - * @return int - * @access public - */ - function cmpAbs($num1, $num2) - { - return bccomp($num1, $num2); - } - - /** - * Tests $num on primality. Returns true, if $num is strong pseudoprime. - * Else returns false. - * - * @param string $num - * @return bool - * @access private - */ - function isPrime($num) - { - static $primes = null; - static $primes_cnt = 0; - if (is_null($primes)) { - // generate all primes up to 10000 - $primes = array(); - for ($i = 0; $i < 10000; $i++) { - $primes[] = $i; - } - $primes[0] = $primes[1] = 0; - for ($i = 2; $i < 100; $i++) { - while (!$primes[$i]) { - $i++; - } - $j = $i; - for ($j += $i; $j < 10000; $j += $i) { - $primes[$j] = 0; - } - } - $j = 0; - for ($i = 0; $i < 10000; $i++) { - if ($primes[$i]) { - $primes[$j++] = $primes[$i]; - } - } - $primes_cnt = $j; - } - - // try to divide number by small primes - for ($i = 0; $i < $primes_cnt; $i++) { - if (bccomp($num, $primes[$i]) <= 0) { - // number is prime - return true; - } - if (!bccomp(bcmod($num, $primes[$i]), '0')) { - // number divides by $primes[$i] - return false; - } - } - - /* - try Miller-Rabin's probable-primality test for first - 7 primes as bases - */ - for ($i = 0; $i < 7; $i++) { - if (!$this->_millerTest($num, $primes[$i])) { - // $num is composite - return false; - } - } - // $num is strong pseudoprime - return true; - } - - /** - * Generates prime number with length $bits_cnt - * using $random_generator as random generator function. - * - * @param int $bits_cnt - * @param string $rnd_generator - * @access public - */ - function getPrime($bits_cnt, $random_generator) - { - $bytes_n = intval($bits_cnt / 8); - $bits_n = $bits_cnt % 8; - do { - $str = ''; - for ($i = 0; $i < $bytes_n; $i++) { - $str .= chr(call_user_func($random_generator) & 0xff); - } - $n = call_user_func($random_generator) & 0xff; - $n |= 0x80; - $n >>= 8 - $bits_n; - $str .= chr($n); - $num = $this->bin2int($str); - - // search for the next closest prime number after [$num] - if (!bccomp(bcmod($num, '2'), '0')) { - $num = bcadd($num, '1'); - } - while (!$this->isPrime($num)) { - $num = bcadd($num, '2'); - } - } while ($this->bitLen($num) != $bits_cnt); - return $num; - } - - /** - * Calculates $num - 1 - * - * @param string $num - * @return string - * @access public - */ - function dec($num) - { - return bcsub($num, '1'); - } - - /** - * Returns true, if $num is equal to one. Else returns false - * - * @param string $num - * @return bool - * @access public - */ - function isOne($num) - { - return !bccomp($num, '1'); - } - - /** - * Finds greatest common divider (GCD) of $num1 and $num2 - * - * @param string $num1 - * @param string $num2 - * @return string - * @access public - */ - function GCD($num1, $num2) - { - do { - $tmp = bcmod($num1, $num2); - $num1 = $num2; - $num2 = $tmp; - } while (bccomp($num2, '0')); - return $num1; - } - - /** - * Finds inverse number $inv for $num by modulus $mod, such as: - * $inv * $num = 1 (mod $mod) - * - * @param string $num - * @param string $mod - * @return string - * @access public - */ - function invmod($num, $mod) - { - $x = '1'; - $y = '0'; - $num1 = $mod; - do { - $tmp = bcmod($num, $num1); - $q = bcdiv($num, $num1); - $num = $num1; - $num1 = $tmp; - - $tmp = bcsub($x, bcmul($y, $q)); - $x = $y; - $y = $tmp; - } while (bccomp($num1, '0')); - if (bccomp($x, '0') < 0) { - $x = bcadd($x, $mod); - } - return $x; - } - - /** - * Returns bit length of number $num - * - * @param string $num - * @return int - * @access public - */ - function bitLen($num) - { - $tmp = $this->int2bin($num); - $bit_len = strlen($tmp) * 8; - $tmp = ord($tmp{strlen($tmp) - 1}); - if (!$tmp) { - $bit_len -= 8; - } - else { - while (!($tmp & 0x80)) { - $bit_len--; - $tmp <<= 1; - } - } - return $bit_len; - } - - /** - * Calculates bitwise or of $num1 and $num2, - * starting from bit $start_pos for number $num1 - * - * @param string $num1 - * @param string $num2 - * @param int $start_pos - * @return string - * @access public - */ - function bitOr($num1, $num2, $start_pos) - { - $start_byte = intval($start_pos / 8); - $start_bit = $start_pos % 8; - $tmp1 = $this->int2bin($num1); - - $num2 = bcmul($num2, 1 << $start_bit); - $tmp2 = $this->int2bin($num2); - if ($start_byte < strlen($tmp1)) { - $tmp2 |= substr($tmp1, $start_byte); - $tmp1 = substr($tmp1, 0, $start_byte) . $tmp2; - } - else { - $tmp1 = str_pad($tmp1, $start_byte, "\0") . $tmp2; - } - return $this->bin2int($tmp1); - } - - /** - * Returns part of number $num, starting at bit - * position $start with length $length - * - * @param string $num - * @param int start - * @param int length - * @return string - * @access public - */ - function subint($num, $start, $length) - { - $start_byte = intval($start / 8); - $start_bit = $start % 8; - $byte_length = intval($length / 8); - $bit_length = $length % 8; - if ($bit_length) { - $byte_length++; - } - $num = bcdiv($num, 1 << $start_bit); - $tmp = substr($this->int2bin($num), $start_byte, $byte_length); - $tmp = str_pad($tmp, $byte_length, "\0"); - $tmp = substr_replace($tmp, $tmp{$byte_length - 1} & chr(0xff >> (8 - $bit_length)), $byte_length - 1, 1); - return $this->bin2int($tmp); - } - - /** - * Returns name of current wrapper - * - * @return string name of current wrapper - * @access public - */ - function getWrapperName() - { - return 'BCMath'; - } -} - -?> \ No newline at end of file diff --git a/plugins/OStatus/extlib/Crypt/RSA/Math/BigInt.php b/plugins/OStatus/extlib/Crypt/RSA/Math/BigInt.php deleted file mode 100644 index b7ac24cb6..000000000 --- a/plugins/OStatus/extlib/Crypt/RSA/Math/BigInt.php +++ /dev/null @@ -1,313 +0,0 @@ - - * @copyright 2005, 2006 Alexander Valyalkin - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version 1.2.0b - * @link http://pear.php.net/package/Crypt_RSA - */ - -/** - * Crypt_RSA_Math_BigInt class. - * - * Provides set of math functions, which are used by Crypt_RSA package - * This class is a wrapper for big_int PECL extension, - * which could be loaded from http://pecl.php.net/packages/big_int - * - * @category Encryption - * @package Crypt_RSA - * @author Alexander Valyalkin - * @copyright 2005, 2006 Alexander Valyalkin - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @link http://pear.php.net/package/Crypt_RSA - * @version @package_version@ - * @access public - */ -class Crypt_RSA_Math_BigInt -{ - /** - * error description - * - * @var string - * @access public - */ - var $errstr = ''; - - /** - * Crypt_RSA_Math_BigInt constructor. - * Checks an existance of big_int PECL math package. - * This package is available at http://pecl.php.net/packages/big_int - * On failure saves error description in $this->errstr - * - * @access public - */ - function Crypt_RSA_Math_BigInt() - { - if (!extension_loaded('big_int')) { - if (!@dl('big_int.' . PHP_SHLIB_SUFFIX) && !@dl('php_big_int.' . PHP_SHLIB_SUFFIX)) { - // cannot load big_int extension - $this->errstr = 'Crypt_RSA package requires big_int PECL package. ' . - 'It is available at http://pecl.php.net/packages/big_int'; - return; - } - } - - // check version of big_int extension ( Crypt_RSA requires version 1.0.2 and higher ) - if (!in_array('bi_info', get_extension_funcs('big_int'))) { - // there is no bi_info() function in versions, older than 1.0.2 - $this->errstr = 'Crypt_RSA package requires big_int package version 1.0.2 and higher'; - } - } - - /** - * Transforms binary representation of large integer into its native form. - * - * Example of transformation: - * $str = "\x12\x34\x56\x78\x90"; - * $num = 0x9078563412; - * - * @param string $str - * @return big_int resource - * @access public - */ - function bin2int($str) - { - return bi_unserialize($str); - } - - /** - * Transforms large integer into binary representation. - * - * Example of transformation: - * $num = 0x9078563412; - * $str = "\x12\x34\x56\x78\x90"; - * - * @param big_int resource $num - * @return string - * @access public - */ - function int2bin($num) - { - return bi_serialize($num); - } - - /** - * Calculates pow($num, $pow) (mod $mod) - * - * @param big_int resource $num - * @param big_int resource $pow - * @param big_int resource $mod - * @return big_int resource - * @access public - */ - function powmod($num, $pow, $mod) - { - return bi_powmod($num, $pow, $mod); - } - - /** - * Calculates $num1 * $num2 - * - * @param big_int resource $num1 - * @param big_int resource $num2 - * @return big_int resource - * @access public - */ - function mul($num1, $num2) - { - return bi_mul($num1, $num2); - } - - /** - * Calculates $num1 % $num2 - * - * @param string $num1 - * @param string $num2 - * @return string - * @access public - */ - function mod($num1, $num2) - { - return bi_mod($num1, $num2); - } - - /** - * Compares abs($num1) to abs($num2). - * Returns: - * -1, if abs($num1) < abs($num2) - * 0, if abs($num1) == abs($num2) - * 1, if abs($num1) > abs($num2) - * - * @param big_int resource $num1 - * @param big_int resource $num2 - * @return int - * @access public - */ - function cmpAbs($num1, $num2) - { - return bi_cmp_abs($num1, $num2); - } - - /** - * Tests $num on primality. Returns true, if $num is strong pseudoprime. - * Else returns false. - * - * @param string $num - * @return bool - * @access private - */ - function isPrime($num) - { - return bi_is_prime($num) ? true : false; - } - - /** - * Generates prime number with length $bits_cnt - * using $random_generator as random generator function. - * - * @param int $bits_cnt - * @param string $rnd_generator - * @access public - */ - function getPrime($bits_cnt, $random_generator) - { - $bytes_n = intval($bits_cnt / 8); - $bits_n = $bits_cnt % 8; - do { - $str = ''; - for ($i = 0; $i < $bytes_n; $i++) { - $str .= chr(call_user_func($random_generator) & 0xff); - } - $n = call_user_func($random_generator) & 0xff; - $n |= 0x80; - $n >>= 8 - $bits_n; - $str .= chr($n); - $num = $this->bin2int($str); - - // search for the next closest prime number after [$num] - $num = bi_next_prime($num); - } while ($this->bitLen($num) != $bits_cnt); - return $num; - } - - /** - * Calculates $num - 1 - * - * @param big_int resource $num - * @return big_int resource - * @access public - */ - function dec($num) - { - return bi_dec($num); - } - - /** - * Returns true, if $num is equal to 1. Else returns false - * - * @param big_int resource $num - * @return bool - * @access public - */ - function isOne($num) - { - return bi_is_one($num); - } - - /** - * Finds greatest common divider (GCD) of $num1 and $num2 - * - * @param big_int resource $num1 - * @param big_int resource $num2 - * @return big_int resource - * @access public - */ - function GCD($num1, $num2) - { - return bi_gcd($num1, $num2); - } - - /** - * Finds inverse number $inv for $num by modulus $mod, such as: - * $inv * $num = 1 (mod $mod) - * - * @param big_int resource $num - * @param big_int resource $mod - * @return big_int resource - * @access public - */ - function invmod($num, $mod) - { - return bi_invmod($num, $mod); - } - - /** - * Returns bit length of number $num - * - * @param big_int resource $num - * @return int - * @access public - */ - function bitLen($num) - { - return bi_bit_len($num); - } - - /** - * Calculates bitwise or of $num1 and $num2, - * starting from bit $start_pos for number $num1 - * - * @param big_int resource $num1 - * @param big_int resource $num2 - * @param int $start_pos - * @return big_int resource - * @access public - */ - function bitOr($num1, $num2, $start_pos) - { - return bi_or($num1, $num2, $start_pos); - } - - /** - * Returns part of number $num, starting at bit - * position $start with length $length - * - * @param big_int resource $num - * @param int start - * @param int length - * @return big_int resource - * @access public - */ - function subint($num, $start, $length) - { - return bi_subint($num, $start, $length); - } - - /** - * Returns name of current wrapper - * - * @return string name of current wrapper - * @access public - */ - function getWrapperName() - { - return 'BigInt'; - } -} - -?> \ No newline at end of file diff --git a/plugins/OStatus/extlib/Crypt/RSA/Math/GMP.php b/plugins/OStatus/extlib/Crypt/RSA/Math/GMP.php deleted file mode 100644 index 54e4c34fc..000000000 --- a/plugins/OStatus/extlib/Crypt/RSA/Math/GMP.php +++ /dev/null @@ -1,361 +0,0 @@ - - * @copyright 2005, 2006 Alexander Valyalkin - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version 1.2.0b - * @link http://pear.php.net/package/Crypt_RSA - */ - -/** - * Crypt_RSA_Math_GMP class. - * - * Provides set of math functions, which are used by Crypt_RSA package - * This class is a wrapper for PHP GMP extension. - * See http://php.net/gmp for details. - * - * @category Encryption - * @package Crypt_RSA - * @author Alexander Valyalkin - * @copyright 2005, 2006 Alexander Valyalkin - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @link http://pear.php.net/package/Crypt_RSA - * @version @package_version@ - * @access public - */ -class Crypt_RSA_Math_GMP -{ - /** - * error description - * - * @var string - * @access public - */ - var $errstr = ''; - - /** - * Crypt_RSA_Math_GMP constructor. - * Checks an existance of PHP GMP package. - * See http://php.net/gmp for details. - * - * On failure saves error description in $this->errstr - * - * @access public - */ - function Crypt_RSA_Math_GMP() - { - if (!extension_loaded('gmp')) { - if (!@dl('gmp.' . PHP_SHLIB_SUFFIX) && !@dl('php_gmp.' . PHP_SHLIB_SUFFIX)) { - // cannot load GMP extension - $this->errstr = 'Crypt_RSA package requires PHP GMP package. ' . - 'See http://php.net/gmp for details'; - return; - } - } - } - - /** - * Transforms binary representation of large integer into its native form. - * - * Example of transformation: - * $str = "\x12\x34\x56\x78\x90"; - * $num = 0x9078563412; - * - * @param string $str - * @return gmp resource - * @access public - */ - function bin2int($str) - { - $result = 0; - $n = strlen($str); - do { - // dirty hack: GMP returns FALSE, when second argument equals to int(0). - // so, it must be converted to string '0' - $result = gmp_add(gmp_mul($result, 256), strval(ord($str{--$n}))); - } while ($n > 0); - return $result; - } - - /** - * Transforms large integer into binary representation. - * - * Example of transformation: - * $num = 0x9078563412; - * $str = "\x12\x34\x56\x78\x90"; - * - * @param gmp resource $num - * @return string - * @access public - */ - function int2bin($num) - { - $result = ''; - do { - $result .= chr(gmp_intval(gmp_mod($num, 256))); - $num = gmp_div($num, 256); - } while (gmp_cmp($num, 0)); - return $result; - } - - /** - * Calculates pow($num, $pow) (mod $mod) - * - * @param gmp resource $num - * @param gmp resource $pow - * @param gmp resource $mod - * @return gmp resource - * @access public - */ - function powmod($num, $pow, $mod) - { - return gmp_powm($num, $pow, $mod); - } - - /** - * Calculates $num1 * $num2 - * - * @param gmp resource $num1 - * @param gmp resource $num2 - * @return gmp resource - * @access public - */ - function mul($num1, $num2) - { - return gmp_mul($num1, $num2); - } - - /** - * Calculates $num1 % $num2 - * - * @param string $num1 - * @param string $num2 - * @return string - * @access public - */ - function mod($num1, $num2) - { - return gmp_mod($num1, $num2); - } - - /** - * Compares abs($num1) to abs($num2). - * Returns: - * -1, if abs($num1) < abs($num2) - * 0, if abs($num1) == abs($num2) - * 1, if abs($num1) > abs($num2) - * - * @param gmp resource $num1 - * @param gmp resource $num2 - * @return int - * @access public - */ - function cmpAbs($num1, $num2) - { - return gmp_cmp($num1, $num2); - } - - /** - * Tests $num on primality. Returns true, if $num is strong pseudoprime. - * Else returns false. - * - * @param string $num - * @return bool - * @access private - */ - function isPrime($num) - { - return gmp_prob_prime($num) ? true : false; - } - - /** - * Generates prime number with length $bits_cnt - * using $random_generator as random generator function. - * - * @param int $bits_cnt - * @param string $rnd_generator - * @access public - */ - function getPrime($bits_cnt, $random_generator) - { - $bytes_n = intval($bits_cnt / 8); - $bits_n = $bits_cnt % 8; - do { - $str = ''; - for ($i = 0; $i < $bytes_n; $i++) { - $str .= chr(call_user_func($random_generator) & 0xff); - } - $n = call_user_func($random_generator) & 0xff; - $n |= 0x80; - $n >>= 8 - $bits_n; - $str .= chr($n); - $num = $this->bin2int($str); - - // search for the next closest prime number after [$num] - if (!gmp_cmp(gmp_mod($num, '2'), '0')) { - $num = gmp_add($num, '1'); - } - while (!gmp_prob_prime($num)) { - $num = gmp_add($num, '2'); - } - } while ($this->bitLen($num) != $bits_cnt); - return $num; - } - - /** - * Calculates $num - 1 - * - * @param gmp resource $num - * @return gmp resource - * @access public - */ - function dec($num) - { - return gmp_sub($num, 1); - } - - /** - * Returns true, if $num is equal to one. Else returns false - * - * @param gmp resource $num - * @return bool - * @access public - */ - function isOne($num) - { - return !gmp_cmp($num, 1); - } - - /** - * Finds greatest common divider (GCD) of $num1 and $num2 - * - * @param gmp resource $num1 - * @param gmp resource $num2 - * @return gmp resource - * @access public - */ - function GCD($num1, $num2) - { - return gmp_gcd($num1, $num2); - } - - /** - * Finds inverse number $inv for $num by modulus $mod, such as: - * $inv * $num = 1 (mod $mod) - * - * @param gmp resource $num - * @param gmp resource $mod - * @return gmp resource - * @access public - */ - function invmod($num, $mod) - { - return gmp_invert($num, $mod); - } - - /** - * Returns bit length of number $num - * - * @param gmp resource $num - * @return int - * @access public - */ - function bitLen($num) - { - $tmp = $this->int2bin($num); - $bit_len = strlen($tmp) * 8; - $tmp = ord($tmp{strlen($tmp) - 1}); - if (!$tmp) { - $bit_len -= 8; - } - else { - while (!($tmp & 0x80)) { - $bit_len--; - $tmp <<= 1; - } - } - return $bit_len; - } - - /** - * Calculates bitwise or of $num1 and $num2, - * starting from bit $start_pos for number $num1 - * - * @param gmp resource $num1 - * @param gmp resource $num2 - * @param int $start_pos - * @return gmp resource - * @access public - */ - function bitOr($num1, $num2, $start_pos) - { - $start_byte = intval($start_pos / 8); - $start_bit = $start_pos % 8; - $tmp1 = $this->int2bin($num1); - - $num2 = gmp_mul($num2, 1 << $start_bit); - $tmp2 = $this->int2bin($num2); - if ($start_byte < strlen($tmp1)) { - $tmp2 |= substr($tmp1, $start_byte); - $tmp1 = substr($tmp1, 0, $start_byte) . $tmp2; - } - else { - $tmp1 = str_pad($tmp1, $start_byte, "\0") . $tmp2; - } - return $this->bin2int($tmp1); - } - - /** - * Returns part of number $num, starting at bit - * position $start with length $length - * - * @param gmp resource $num - * @param int start - * @param int length - * @return gmp resource - * @access public - */ - function subint($num, $start, $length) - { - $start_byte = intval($start / 8); - $start_bit = $start % 8; - $byte_length = intval($length / 8); - $bit_length = $length % 8; - if ($bit_length) { - $byte_length++; - } - $num = gmp_div($num, 1 << $start_bit); - $tmp = substr($this->int2bin($num), $start_byte, $byte_length); - $tmp = str_pad($tmp, $byte_length, "\0"); - $tmp = substr_replace($tmp, $tmp{$byte_length - 1} & chr(0xff >> (8 - $bit_length)), $byte_length - 1, 1); - return $this->bin2int($tmp); - } - - /** - * Returns name of current wrapper - * - * @return string name of current wrapper - * @access public - */ - function getWrapperName() - { - return 'GMP'; - } -} - -?> \ No newline at end of file diff --git a/plugins/OStatus/extlib/Crypt/RSA/MathLoader.php b/plugins/OStatus/extlib/Crypt/RSA/MathLoader.php deleted file mode 100644 index de6c94642..000000000 --- a/plugins/OStatus/extlib/Crypt/RSA/MathLoader.php +++ /dev/null @@ -1,135 +0,0 @@ - - * @copyright Alexander Valyalkin 2005 - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version CVS: $Id: MathLoader.php,v 1.5 2009/01/05 08:30:29 clockwerx Exp $ - * @link http://pear.php.net/package/Crypt_RSA - */ - -/** - * RSA error handling facilities - */ -require_once 'Crypt/RSA/ErrorHandler.php'; - -/** - * Crypt_RSA_MathLoader class. - * - * Provides static function: - * - loadWrapper($wrapper_name) - loads RSA math wrapper with name $wrapper_name - * or most suitable wrapper if $wrapper_name == 'default' - * - * Example usage: - * // load BigInt wrapper - * $big_int_wrapper = Crypt_RSA_MathLoader::loadWrapper('BigInt'); - * - * // load BCMath wrapper - * $bcmath_wrapper = Crypt_RSA_MathLoader::loadWrapper('BCMath'); - * - * // load the most suitable wrapper - * $bcmath_wrapper = Crypt_RSA_MathLoader::loadWrapper(); - * - * @category Encryption - * @package Crypt_RSA - * @author Alexander Valyalkin - * @copyright Alexander Valyalkin 2005 - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version Release: @package_version@ - * @link http://pear.php.net/package/Crypt_RSA - * @access public - */ -class Crypt_RSA_MathLoader -{ - /** - * Loads RSA math wrapper with name $wrapper_name. - * Implemented wrappers can be found at Crypt/RSA/Math folder. - * Read docs/Crypt_RSA/docs/math_wrappers.txt for details - * - * This is a static function: - * // load BigInt wrapper - * $big_int_wrapper = &Crypt_RSA_MathLoader::loadWrapper('BigInt'); - * - * // load BCMath wrapper - * $bcmath_wrapper = &Crypt_RSA_MathLoader::loadWrapper('BCMath'); - * - * @param string $wrapper_name Name of wrapper - * - * @return object - * Reference to object of wrapper with name $wrapper_name on success - * or PEAR_Error object on error - * - * @access public - */ - function loadWrapper($wrapper_name = 'default') - { - static $math_objects = array(); - // ordered by performance. GMP is the fastest math library, BCMath - the slowest. - static $math_wrappers = array('GMP', 'BigInt', 'BCMath',); - - if (isset($math_objects[$wrapper_name])) { - /* - wrapper with name $wrapper_name is already loaded and created. - Return reference to existing copy of wrapper - */ - return $math_objects[$wrapper_name]; - } - - $err_handler = new Crypt_RSA_ErrorHandler(); - - if ($wrapper_name === 'default') { - // try to load the most suitable wrapper - $n = sizeof($math_wrappers); - for ($i = 0; $i < $n; $i++) { - $obj = Crypt_RSA_MathLoader::loadWrapper($math_wrappers[$i]); - if (!$err_handler->isError($obj)) { - // wrapper for $math_wrappers[$i] successfully loaded - // register it as default wrapper and return reference to it - return $math_objects['default'] = $obj; - } - } - // can't load any wrapper - $err_handler->pushError("can't load any wrapper for existing math libraries", CRYPT_RSA_ERROR_NO_WRAPPERS); - return $err_handler->getLastError(); - } - - $class_name = 'Crypt_RSA_Math_' . $wrapper_name; - $class_filename = dirname(__FILE__) . '/Math/' . $wrapper_name . '.php'; - - if (!is_file($class_filename)) { - $err_handler->pushError("can't find file [{$class_filename}] for RSA math wrapper [{$wrapper_name}]", CRYPT_RSA_ERROR_NO_FILE); - return $err_handler->getLastError(); - } - - include_once $class_filename; - if (!class_exists($class_name)) { - $err_handler->pushError("can't find class [{$class_name}] in file [{$class_filename}]", CRYPT_RSA_ERROR_NO_CLASS); - return $err_handler->getLastError(); - } - - // create and return wrapper object on success or PEAR_Error object on error - $obj = new $class_name; - if ($obj->errstr) { - // cannot load required extension for math wrapper - $err_handler->pushError($obj->errstr, CRYPT_RSA_ERROR_NO_EXT); - return $err_handler->getLastError(); - } - return $math_objects[$wrapper_name] = $obj; - } -} - -?> -- cgit v1.2.3-54-g00ecf From 41d2ff662c7783f4aa0341b9b542ff157d01593c Mon Sep 17 00:00:00 2001 From: James Walker Date: Fri, 12 Mar 2010 19:34:24 -0500 Subject: Adding Crypt library from http://phpseclib.sourceforge.net/ --- plugins/OStatus/extlib/Crypt/AES.php | 421 ++++ plugins/OStatus/extlib/Crypt/DES.php | 851 ++++++++ plugins/OStatus/extlib/Crypt/Hash.php | 816 ++++++++ plugins/OStatus/extlib/Crypt/RC4.php | 493 +++++ plugins/OStatus/extlib/Crypt/RSA.php | 1929 ++++++++++++++++++ plugins/OStatus/extlib/Crypt/Random.php | 70 + plugins/OStatus/extlib/Crypt/Rijndael.php | 1135 +++++++++++ plugins/OStatus/extlib/Crypt/TripleDES.php | 603 ++++++ plugins/OStatus/extlib/Math/BigInteger.php | 3060 ++++++++++++++++++++++++++++ 9 files changed, 9378 insertions(+) create mode 100644 plugins/OStatus/extlib/Crypt/AES.php create mode 100644 plugins/OStatus/extlib/Crypt/DES.php create mode 100644 plugins/OStatus/extlib/Crypt/Hash.php create mode 100644 plugins/OStatus/extlib/Crypt/RC4.php create mode 100644 plugins/OStatus/extlib/Crypt/RSA.php create mode 100644 plugins/OStatus/extlib/Crypt/Random.php create mode 100644 plugins/OStatus/extlib/Crypt/Rijndael.php create mode 100644 plugins/OStatus/extlib/Crypt/TripleDES.php create mode 100644 plugins/OStatus/extlib/Math/BigInteger.php (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/extlib/Crypt/AES.php b/plugins/OStatus/extlib/Crypt/AES.php new file mode 100644 index 000000000..4b062c4f2 --- /dev/null +++ b/plugins/OStatus/extlib/Crypt/AES.php @@ -0,0 +1,421 @@ + + * setKey('abcdefghijklmnop'); + * + * $size = 10 * 1024; + * $plaintext = ''; + * for ($i = 0; $i < $size; $i++) { + * $plaintext.= 'a'; + * } + * + * echo $aes->decrypt($aes->encrypt($plaintext)); + * ?> + * + * + * LICENSE: This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * @category Crypt + * @package Crypt_AES + * @author Jim Wigginton + * @copyright MMVIII Jim Wigginton + * @license http://www.gnu.org/licenses/lgpl.txt + * @version $Id: AES.php,v 1.5 2009/11/23 19:06:06 terrafrost Exp $ + * @link http://phpseclib.sourceforge.net + */ + +/** + * Include Crypt_Rijndael + */ +require_once 'Rijndael.php'; + +/**#@+ + * @access public + * @see Crypt_AES::encrypt() + * @see Crypt_AES::decrypt() + */ +/** + * Encrypt / decrypt using the Electronic Code Book mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29 + */ +define('CRYPT_AES_MODE_ECB', 1); +/** + * Encrypt / decrypt using the Code Book Chaining mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29 + */ +define('CRYPT_AES_MODE_CBC', 2); +/**#@-*/ + +/**#@+ + * @access private + * @see Crypt_AES::Crypt_AES() + */ +/** + * Toggles the internal implementation + */ +define('CRYPT_AES_MODE_INTERNAL', 1); +/** + * Toggles the mcrypt implementation + */ +define('CRYPT_AES_MODE_MCRYPT', 2); +/**#@-*/ + +/** + * Pure-PHP implementation of AES. + * + * @author Jim Wigginton + * @version 0.1.0 + * @access public + * @package Crypt_AES + */ +class Crypt_AES extends Crypt_Rijndael { + /** + * MCrypt parameters + * + * @see Crypt_AES::setMCrypt() + * @var Array + * @access private + */ + var $mcrypt = array('', ''); + + /** + * Default Constructor. + * + * Determines whether or not the mcrypt extension should be used. $mode should only, at present, be + * CRYPT_AES_MODE_ECB or CRYPT_AES_MODE_CBC. If not explictly set, CRYPT_AES_MODE_CBC will be used. + * + * @param optional Integer $mode + * @return Crypt_AES + * @access public + */ + function Crypt_AES($mode = CRYPT_AES_MODE_CBC) + { + if ( !defined('CRYPT_AES_MODE') ) { + switch (true) { + case extension_loaded('mcrypt'): + // i'd check to see if aes was supported, by doing in_array('des', mcrypt_list_algorithms('')), + // but since that can be changed after the object has been created, there doesn't seem to be + // a lot of point... + define('CRYPT_AES_MODE', CRYPT_AES_MODE_MCRYPT); + break; + default: + define('CRYPT_AES_MODE', CRYPT_AES_MODE_INTERNAL); + } + } + + switch ( CRYPT_AES_MODE ) { + case CRYPT_AES_MODE_MCRYPT: + switch ($mode) { + case CRYPT_AES_MODE_ECB: + $this->mode = MCRYPT_MODE_ECB; + break; + case CRYPT_AES_MODE_CBC: + default: + $this->mode = MCRYPT_MODE_CBC; + } + + break; + default: + switch ($mode) { + case CRYPT_AES_MODE_ECB: + $this->mode = CRYPT_RIJNDAEL_MODE_ECB; + break; + case CRYPT_AES_MODE_CBC: + default: + $this->mode = CRYPT_RIJNDAEL_MODE_CBC; + } + } + + if (CRYPT_AES_MODE == CRYPT_AES_MODE_INTERNAL) { + parent::Crypt_Rijndael($this->mode); + } + } + + /** + * Dummy function + * + * Since Crypt_AES extends Crypt_Rijndael, this function is, technically, available, but it doesn't do anything. + * + * @access public + * @param Integer $length + */ + function setBlockLength($length) + { + return; + } + + /** + * Encrypts a message. + * + * $plaintext will be padded with up to 16 additional bytes. Other AES implementations may or may not pad in the + * same manner. Other common approaches to padding and the reasons why it's necessary are discussed in the following + * URL: + * + * {@link http://www.di-mgt.com.au/cryptopad.html http://www.di-mgt.com.au/cryptopad.html} + * + * An alternative to padding is to, separately, send the length of the file. This is what SSH, in fact, does. + * strlen($plaintext) will still need to be a multiple of 16, however, arbitrary values can be added to make it that + * length. + * + * @see Crypt_AES::decrypt() + * @access public + * @param String $plaintext + */ + function encrypt($plaintext) + { + if ( CRYPT_AES_MODE == CRYPT_AES_MODE_MCRYPT ) { + $this->_mcryptSetup(); + $plaintext = $this->_pad($plaintext); + + $td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, $this->mcrypt[0], $this->mode, $this->mcrypt[1]); + mcrypt_generic_init($td, $this->key, $this->encryptIV); + + $ciphertext = mcrypt_generic($td, $plaintext); + + mcrypt_generic_deinit($td); + mcrypt_module_close($td); + + if ($this->continuousBuffer) { + $this->encryptIV = substr($ciphertext, -16); + } + + return $ciphertext; + } + + return parent::encrypt($plaintext); + } + + /** + * Decrypts a message. + * + * If strlen($ciphertext) is not a multiple of 16, null bytes will be added to the end of the string until it is. + * + * @see Crypt_AES::encrypt() + * @access public + * @param String $ciphertext + */ + function decrypt($ciphertext) + { + // we pad with chr(0) since that's what mcrypt_generic does. to quote from http://php.net/function.mcrypt-generic : + // "The data is padded with "\0" to make sure the length of the data is n * blocksize." + $ciphertext = str_pad($ciphertext, (strlen($ciphertext) + 15) & 0xFFFFFFF0, chr(0)); + + if ( CRYPT_AES_MODE == CRYPT_AES_MODE_MCRYPT ) { + $this->_mcryptSetup(); + + $td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, $this->mcrypt[0], $this->mode, $this->mcrypt[1]); + mcrypt_generic_init($td, $this->key, $this->decryptIV); + + $plaintext = mdecrypt_generic($td, $ciphertext); + + mcrypt_generic_deinit($td); + mcrypt_module_close($td); + + if ($this->continuousBuffer) { + $this->decryptIV = substr($ciphertext, -16); + } + + return $this->_unpad($plaintext); + } + + return parent::decrypt($ciphertext); + } + + /** + * Sets MCrypt parameters. (optional) + * + * If MCrypt is being used, empty strings will be used, unless otherwise specified. + * + * @link http://php.net/function.mcrypt-module-open#function.mcrypt-module-open + * @access public + * @param optional Integer $algorithm_directory + * @param optional Integer $mode_directory + */ + function setMCrypt($algorithm_directory = '', $mode_directory = '') + { + $this->mcrypt = array($algorithm_directory, $mode_directory); + } + + /** + * Setup mcrypt + * + * Validates all the variables. + * + * @access private + */ + function _mcryptSetup() + { + if (!$this->changed) { + return; + } + + if (!$this->explicit_key_length) { + // this just copied from Crypt_Rijndael::_setup() + $length = strlen($this->key) >> 2; + if ($length > 8) { + $length = 8; + } else if ($length < 4) { + $length = 4; + } + $this->Nk = $length; + $this->key_size = $length << 2; + } + + switch ($this->Nk) { + case 4: // 128 + $this->key_size = 16; + break; + case 5: // 160 + case 6: // 192 + $this->key_size = 24; + break; + case 7: // 224 + case 8: // 256 + $this->key_size = 32; + } + + $this->key = substr($this->key, 0, $this->key_size); + $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($this->iv, 0, 16), 16, chr(0)); + + $this->changed = false; + } + + /** + * Encrypts a block + * + * Optimized over Crypt_Rijndael's implementation by means of loop unrolling. + * + * @see Crypt_Rijndael::_encryptBlock() + * @access private + * @param String $in + * @return String + */ + function _encryptBlock($in) + { + $state = unpack('N*word', $in); + + // addRoundKey and reindex $state + $state = array( + $state['word1'] ^ $this->w[0][0], + $state['word2'] ^ $this->w[0][1], + $state['word3'] ^ $this->w[0][2], + $state['word4'] ^ $this->w[0][3] + ); + + // shiftRows + subWord + mixColumns + addRoundKey + // we could loop unroll this and use if statements to do more rounds as necessary, but, in my tests, that yields + // only a marginal improvement. since that also, imho, hinders the readability of the code, i've opted not to do it. + for ($round = 1; $round < $this->Nr; $round++) { + $state = array( + $this->t0[$state[0] & 0xFF000000] ^ $this->t1[$state[1] & 0x00FF0000] ^ $this->t2[$state[2] & 0x0000FF00] ^ $this->t3[$state[3] & 0x000000FF] ^ $this->w[$round][0], + $this->t0[$state[1] & 0xFF000000] ^ $this->t1[$state[2] & 0x00FF0000] ^ $this->t2[$state[3] & 0x0000FF00] ^ $this->t3[$state[0] & 0x000000FF] ^ $this->w[$round][1], + $this->t0[$state[2] & 0xFF000000] ^ $this->t1[$state[3] & 0x00FF0000] ^ $this->t2[$state[0] & 0x0000FF00] ^ $this->t3[$state[1] & 0x000000FF] ^ $this->w[$round][2], + $this->t0[$state[3] & 0xFF000000] ^ $this->t1[$state[0] & 0x00FF0000] ^ $this->t2[$state[1] & 0x0000FF00] ^ $this->t3[$state[2] & 0x000000FF] ^ $this->w[$round][3] + ); + + } + + // subWord + $state = array( + $this->_subWord($state[0]), + $this->_subWord($state[1]), + $this->_subWord($state[2]), + $this->_subWord($state[3]) + ); + + // shiftRows + addRoundKey + $state = array( + ($state[0] & 0xFF000000) ^ ($state[1] & 0x00FF0000) ^ ($state[2] & 0x0000FF00) ^ ($state[3] & 0x000000FF) ^ $this->w[$this->Nr][0], + ($state[1] & 0xFF000000) ^ ($state[2] & 0x00FF0000) ^ ($state[3] & 0x0000FF00) ^ ($state[0] & 0x000000FF) ^ $this->w[$this->Nr][1], + ($state[2] & 0xFF000000) ^ ($state[3] & 0x00FF0000) ^ ($state[0] & 0x0000FF00) ^ ($state[1] & 0x000000FF) ^ $this->w[$this->Nr][2], + ($state[3] & 0xFF000000) ^ ($state[0] & 0x00FF0000) ^ ($state[1] & 0x0000FF00) ^ ($state[2] & 0x000000FF) ^ $this->w[$this->Nr][3] + ); + + return pack('N*', $state[0], $state[1], $state[2], $state[3]); + } + + /** + * Decrypts a block + * + * Optimized over Crypt_Rijndael's implementation by means of loop unrolling. + * + * @see Crypt_Rijndael::_decryptBlock() + * @access private + * @param String $in + * @return String + */ + function _decryptBlock($in) + { + $state = unpack('N*word', $in); + + // addRoundKey and reindex $state + $state = array( + $state['word1'] ^ $this->dw[$this->Nr][0], + $state['word2'] ^ $this->dw[$this->Nr][1], + $state['word3'] ^ $this->dw[$this->Nr][2], + $state['word4'] ^ $this->dw[$this->Nr][3] + ); + + + // invShiftRows + invSubBytes + invMixColumns + addRoundKey + for ($round = $this->Nr - 1; $round > 0; $round--) { + $state = array( + $this->dt0[$state[0] & 0xFF000000] ^ $this->dt1[$state[3] & 0x00FF0000] ^ $this->dt2[$state[2] & 0x0000FF00] ^ $this->dt3[$state[1] & 0x000000FF] ^ $this->dw[$round][0], + $this->dt0[$state[1] & 0xFF000000] ^ $this->dt1[$state[0] & 0x00FF0000] ^ $this->dt2[$state[3] & 0x0000FF00] ^ $this->dt3[$state[2] & 0x000000FF] ^ $this->dw[$round][1], + $this->dt0[$state[2] & 0xFF000000] ^ $this->dt1[$state[1] & 0x00FF0000] ^ $this->dt2[$state[0] & 0x0000FF00] ^ $this->dt3[$state[3] & 0x000000FF] ^ $this->dw[$round][2], + $this->dt0[$state[3] & 0xFF000000] ^ $this->dt1[$state[2] & 0x00FF0000] ^ $this->dt2[$state[1] & 0x0000FF00] ^ $this->dt3[$state[0] & 0x000000FF] ^ $this->dw[$round][3] + ); + } + + // invShiftRows + invSubWord + addRoundKey + $state = array( + $this->_invSubWord(($state[0] & 0xFF000000) ^ ($state[3] & 0x00FF0000) ^ ($state[2] & 0x0000FF00) ^ ($state[1] & 0x000000FF)) ^ $this->dw[0][0], + $this->_invSubWord(($state[1] & 0xFF000000) ^ ($state[0] & 0x00FF0000) ^ ($state[3] & 0x0000FF00) ^ ($state[2] & 0x000000FF)) ^ $this->dw[0][1], + $this->_invSubWord(($state[2] & 0xFF000000) ^ ($state[1] & 0x00FF0000) ^ ($state[0] & 0x0000FF00) ^ ($state[3] & 0x000000FF)) ^ $this->dw[0][2], + $this->_invSubWord(($state[3] & 0xFF000000) ^ ($state[2] & 0x00FF0000) ^ ($state[1] & 0x0000FF00) ^ ($state[0] & 0x000000FF)) ^ $this->dw[0][3] + ); + + return pack('N*', $state[0], $state[1], $state[2], $state[3]); + } +} + +// vim: ts=4:sw=4:et: +// vim6: fdl=1: \ No newline at end of file diff --git a/plugins/OStatus/extlib/Crypt/DES.php b/plugins/OStatus/extlib/Crypt/DES.php new file mode 100644 index 000000000..3fd0b65ec --- /dev/null +++ b/plugins/OStatus/extlib/Crypt/DES.php @@ -0,0 +1,851 @@ + + * setKey('abcdefgh'); + * + * $size = 10 * 1024; + * $plaintext = ''; + * for ($i = 0; $i < $size; $i++) { + * $plaintext.= 'a'; + * } + * + * echo $des->decrypt($des->encrypt($plaintext)); + * ?> + * + * + * LICENSE: This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * @category Crypt + * @package Crypt_DES + * @author Jim Wigginton + * @copyright MMVII Jim Wigginton + * @license http://www.gnu.org/licenses/lgpl.txt + * @version $Id: DES.php,v 1.9 2009/11/23 19:06:06 terrafrost Exp $ + * @link http://phpseclib.sourceforge.net + */ + +/**#@+ + * @access private + * @see Crypt_DES::_prepareKey() + * @see Crypt_DES::_processBlock() + */ +/** + * Contains array_reverse($keys[CRYPT_DES_DECRYPT]) + */ +define('CRYPT_DES_ENCRYPT', 0); +/** + * Contains array_reverse($keys[CRYPT_DES_ENCRYPT]) + */ +define('CRYPT_DES_DECRYPT', 1); +/**#@-*/ + +/**#@+ + * @access public + * @see Crypt_DES::encrypt() + * @see Crypt_DES::decrypt() + */ +/** + * Encrypt / decrypt using the Electronic Code Book mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29 + */ +define('CRYPT_DES_MODE_ECB', 1); +/** + * Encrypt / decrypt using the Code Book Chaining mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29 + */ +define('CRYPT_DES_MODE_CBC', 2); +/**#@-*/ + +/**#@+ + * @access private + * @see Crypt_DES::Crypt_DES() + */ +/** + * Toggles the internal implementation + */ +define('CRYPT_DES_MODE_INTERNAL', 1); +/** + * Toggles the mcrypt implementation + */ +define('CRYPT_DES_MODE_MCRYPT', 2); +/**#@-*/ + +/** + * Pure-PHP implementation of DES. + * + * @author Jim Wigginton + * @version 0.1.0 + * @access public + * @package Crypt_DES + */ +class Crypt_DES { + /** + * The Key Schedule + * + * @see Crypt_DES::setKey() + * @var Array + * @access private + */ + var $keys = "\0\0\0\0\0\0\0\0"; + + /** + * The Encryption Mode + * + * @see Crypt_DES::Crypt_DES() + * @var Integer + * @access private + */ + var $mode; + + /** + * Continuous Buffer status + * + * @see Crypt_DES::enableContinuousBuffer() + * @var Boolean + * @access private + */ + var $continuousBuffer = false; + + /** + * Padding status + * + * @see Crypt_DES::enablePadding() + * @var Boolean + * @access private + */ + var $padding = true; + + /** + * The Initialization Vector + * + * @see Crypt_DES::setIV() + * @var String + * @access private + */ + var $iv = "\0\0\0\0\0\0\0\0"; + + /** + * A "sliding" Initialization Vector + * + * @see Crypt_DES::enableContinuousBuffer() + * @var String + * @access private + */ + var $encryptIV = "\0\0\0\0\0\0\0\0"; + + /** + * A "sliding" Initialization Vector + * + * @see Crypt_DES::enableContinuousBuffer() + * @var String + * @access private + */ + var $decryptIV = "\0\0\0\0\0\0\0\0"; + + /** + * MCrypt parameters + * + * @see Crypt_DES::setMCrypt() + * @var Array + * @access private + */ + var $mcrypt = array('', ''); + + /** + * Default Constructor. + * + * Determines whether or not the mcrypt extension should be used. $mode should only, at present, be + * CRYPT_DES_MODE_ECB or CRYPT_DES_MODE_CBC. If not explictly set, CRYPT_DES_MODE_CBC will be used. + * + * @param optional Integer $mode + * @return Crypt_DES + * @access public + */ + function Crypt_DES($mode = CRYPT_MODE_DES_CBC) + { + if ( !defined('CRYPT_DES_MODE') ) { + switch (true) { + case extension_loaded('mcrypt'): + // i'd check to see if des was supported, by doing in_array('des', mcrypt_list_algorithms('')), + // but since that can be changed after the object has been created, there doesn't seem to be + // a lot of point... + define('CRYPT_DES_MODE', CRYPT_DES_MODE_MCRYPT); + break; + default: + define('CRYPT_DES_MODE', CRYPT_DES_MODE_INTERNAL); + } + } + + switch ( CRYPT_DES_MODE ) { + case CRYPT_DES_MODE_MCRYPT: + switch ($mode) { + case CRYPT_DES_MODE_ECB: + $this->mode = MCRYPT_MODE_ECB; + break; + case CRYPT_DES_MODE_CBC: + default: + $this->mode = MCRYPT_MODE_CBC; + } + + break; + default: + switch ($mode) { + case CRYPT_DES_MODE_ECB: + case CRYPT_DES_MODE_CBC: + $this->mode = $mode; + break; + default: + $this->mode = CRYPT_DES_MODE_CBC; + } + } + } + + /** + * Sets the key. + * + * Keys can be of any length. DES, itself, uses 64-bit keys (eg. strlen($key) == 8), however, we + * only use the first eight, if $key has more then eight characters in it, and pad $key with the + * null byte if it is less then eight characters long. + * + * DES also requires that every eighth bit be a parity bit, however, we'll ignore that. + * + * If the key is not explicitly set, it'll be assumed to be all zero's. + * + * @access public + * @param String $key + */ + function setKey($key) + { + $this->keys = ( CRYPT_DES_MODE == CRYPT_DES_MODE_MCRYPT ) ? substr($key, 0, 8) : $this->_prepareKey($key); + } + + /** + * Sets the initialization vector. (optional) + * + * SetIV is not required when CRYPT_DES_MODE_ECB is being used. If not explictly set, it'll be assumed + * to be all zero's. + * + * @access public + * @param String $iv + */ + function setIV($iv) + { + $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($iv, 0, 8), 8, chr(0));; + } + + /** + * Sets MCrypt parameters. (optional) + * + * If MCrypt is being used, empty strings will be used, unless otherwise specified. + * + * @link http://php.net/function.mcrypt-module-open#function.mcrypt-module-open + * @access public + * @param optional Integer $algorithm_directory + * @param optional Integer $mode_directory + */ + function setMCrypt($algorithm_directory = '', $mode_directory = '') + { + $this->mcrypt = array($algorithm_directory, $mode_directory); + } + + /** + * Encrypts a message. + * + * $plaintext will be padded with up to 8 additional bytes. Other DES implementations may or may not pad in the + * same manner. Other common approaches to padding and the reasons why it's necessary are discussed in the following + * URL: + * + * {@link http://www.di-mgt.com.au/cryptopad.html http://www.di-mgt.com.au/cryptopad.html} + * + * An alternative to padding is to, separately, send the length of the file. This is what SSH, in fact, does. + * strlen($plaintext) will still need to be a multiple of 8, however, arbitrary values can be added to make it that + * length. + * + * @see Crypt_DES::decrypt() + * @access public + * @param String $plaintext + */ + function encrypt($plaintext) + { + $plaintext = $this->_pad($plaintext); + + if ( CRYPT_DES_MODE == CRYPT_DES_MODE_MCRYPT ) { + $td = mcrypt_module_open(MCRYPT_DES, $this->mcrypt[0], $this->mode, $this->mcrypt[1]); + mcrypt_generic_init($td, $this->keys, $this->encryptIV); + + $ciphertext = mcrypt_generic($td, $plaintext); + + mcrypt_generic_deinit($td); + mcrypt_module_close($td); + + if ($this->continuousBuffer) { + $this->encryptIV = substr($ciphertext, -8); + } + + return $ciphertext; + } + + if (!is_array($this->keys)) { + $this->keys = $this->_prepareKey("\0\0\0\0\0\0\0\0"); + } + + $ciphertext = ''; + switch ($this->mode) { + case CRYPT_DES_MODE_ECB: + for ($i = 0; $i < strlen($plaintext); $i+=8) { + $ciphertext.= $this->_processBlock(substr($plaintext, $i, 8), CRYPT_DES_ENCRYPT); + } + break; + case CRYPT_DES_MODE_CBC: + $xor = $this->encryptIV; + for ($i = 0; $i < strlen($plaintext); $i+=8) { + $block = substr($plaintext, $i, 8); + $block = $this->_processBlock($block ^ $xor, CRYPT_DES_ENCRYPT); + $xor = $block; + $ciphertext.= $block; + } + if ($this->continuousBuffer) { + $this->encryptIV = $xor; + } + } + + return $ciphertext; + } + + /** + * Decrypts a message. + * + * If strlen($ciphertext) is not a multiple of 8, null bytes will be added to the end of the string until it is. + * + * @see Crypt_DES::encrypt() + * @access public + * @param String $ciphertext + */ + function decrypt($ciphertext) + { + // we pad with chr(0) since that's what mcrypt_generic does. to quote from http://php.net/function.mcrypt-generic : + // "The data is padded with "\0" to make sure the length of the data is n * blocksize." + $ciphertext = str_pad($ciphertext, (strlen($ciphertext) + 7) & 0xFFFFFFF8, chr(0)); + + if ( CRYPT_DES_MODE == CRYPT_DES_MODE_MCRYPT ) { + $td = mcrypt_module_open(MCRYPT_DES, $this->mcrypt[0], $this->mode, $this->mcrypt[1]); + mcrypt_generic_init($td, $this->keys, $this->decryptIV); + + $plaintext = mdecrypt_generic($td, $ciphertext); + + mcrypt_generic_deinit($td); + mcrypt_module_close($td); + + if ($this->continuousBuffer) { + $this->decryptIV = substr($ciphertext, -8); + } + + return $this->_unpad($plaintext); + } + + if (!is_array($this->keys)) { + $this->keys = $this->_prepareKey("\0\0\0\0\0\0\0\0"); + } + + $plaintext = ''; + switch ($this->mode) { + case CRYPT_DES_MODE_ECB: + for ($i = 0; $i < strlen($ciphertext); $i+=8) { + $plaintext.= $this->_processBlock(substr($ciphertext, $i, 8), CRYPT_DES_DECRYPT); + } + break; + case CRYPT_DES_MODE_CBC: + $xor = $this->decryptIV; + for ($i = 0; $i < strlen($ciphertext); $i+=8) { + $block = substr($ciphertext, $i, 8); + $plaintext.= $this->_processBlock($block, CRYPT_DES_DECRYPT) ^ $xor; + $xor = $block; + } + if ($this->continuousBuffer) { + $this->decryptIV = $xor; + } + } + + return $this->_unpad($plaintext); + } + + /** + * Treat consecutive "packets" as if they are a continuous buffer. + * + * Say you have a 16-byte plaintext $plaintext. Using the default behavior, the two following code snippets + * will yield different outputs: + * + * + * echo $des->encrypt(substr($plaintext, 0, 8)); + * echo $des->encrypt(substr($plaintext, 8, 8)); + * + * + * echo $des->encrypt($plaintext); + * + * + * The solution is to enable the continuous buffer. Although this will resolve the above discrepancy, it creates + * another, as demonstrated with the following: + * + * + * $des->encrypt(substr($plaintext, 0, 8)); + * echo $des->decrypt($des->encrypt(substr($plaintext, 8, 8))); + * + * + * echo $des->decrypt($des->encrypt(substr($plaintext, 8, 8))); + * + * + * With the continuous buffer disabled, these would yield the same output. With it enabled, they yield different + * outputs. The reason is due to the fact that the initialization vector's change after every encryption / + * decryption round when the continuous buffer is enabled. When it's disabled, they remain constant. + * + * Put another way, when the continuous buffer is enabled, the state of the Crypt_DES() object changes after each + * encryption / decryption round, whereas otherwise, it'd remain constant. For this reason, it's recommended that + * continuous buffers not be used. They do offer better security and are, in fact, sometimes required (SSH uses them), + * however, they are also less intuitive and more likely to cause you problems. + * + * @see Crypt_DES::disableContinuousBuffer() + * @access public + */ + function enableContinuousBuffer() + { + $this->continuousBuffer = true; + } + + /** + * Treat consecutive packets as if they are a discontinuous buffer. + * + * The default behavior. + * + * @see Crypt_DES::enableContinuousBuffer() + * @access public + */ + function disableContinuousBuffer() + { + $this->continuousBuffer = false; + $this->encryptIV = $this->iv; + $this->decryptIV = $this->iv; + } + + /** + * Pad "packets". + * + * DES works by encrypting eight bytes at a time. If you ever need to encrypt or decrypt something that's not + * a multiple of eight, it becomes necessary to pad the input so that it's length is a multiple of eight. + * + * Padding is enabled by default. Sometimes, however, it is undesirable to pad strings. Such is the case in SSH1, + * where "packets" are padded with random bytes before being encrypted. Unpad these packets and you risk stripping + * away characters that shouldn't be stripped away. (SSH knows how many bytes are added because the length is + * transmitted separately) + * + * @see Crypt_DES::disablePadding() + * @access public + */ + function enablePadding() + { + $this->padding = true; + } + + /** + * Do not pad packets. + * + * @see Crypt_DES::enablePadding() + * @access public + */ + function disablePadding() + { + $this->padding = false; + } + + /** + * Pads a string + * + * Pads a string using the RSA PKCS padding standards so that its length is a multiple of the blocksize (8). + * 8 - (strlen($text) & 7) bytes are added, each of which is equal to chr(8 - (strlen($text) & 7) + * + * If padding is disabled and $text is not a multiple of the blocksize, the string will be padded regardless + * and padding will, hence forth, be enabled. + * + * @see Crypt_DES::_unpad() + * @access private + */ + function _pad($text) + { + $length = strlen($text); + + if (!$this->padding) { + if (($length & 7) == 0) { + return $text; + } else { + user_error("The plaintext's length ($length) is not a multiple of the block size (8)", E_USER_NOTICE); + $this->padding = true; + } + } + + $pad = 8 - ($length & 7); + return str_pad($text, $length + $pad, chr($pad)); + } + + /** + * Unpads a string + * + * If padding is enabled and the reported padding length is invalid, padding will be, hence forth, disabled. + * + * @see Crypt_DES::_pad() + * @access private + */ + function _unpad($text) + { + if (!$this->padding) { + return $text; + } + + $length = ord($text[strlen($text) - 1]); + + if (!$length || $length > 8) { + user_error("The number of bytes reported as being padded ($length) is invalid (block size = 8)", E_USER_NOTICE); + $this->padding = false; + return $text; + } + + return substr($text, 0, -$length); + } + + /** + * Encrypts or decrypts a 64-bit block + * + * $mode should be either CRYPT_DES_ENCRYPT or CRYPT_DES_DECRYPT. See + * {@link http://en.wikipedia.org/wiki/Image:Feistel.png Feistel.png} to get a general + * idea of what this function does. + * + * @access private + * @param String $block + * @param Integer $mode + * @return String + */ + function _processBlock($block, $mode) + { + // s-boxes. in the official DES docs, they're described as being matrices that + // one accesses by using the first and last bits to determine the row and the + // middle four bits to determine the column. in this implementation, they've + // been converted to vectors + static $sbox = array( + array( + 14, 0, 4, 15, 13, 7, 1, 4, 2, 14, 15, 2, 11, 13, 8, 1, + 3, 10 ,10, 6, 6, 12, 12, 11, 5, 9, 9, 5, 0, 3, 7, 8, + 4, 15, 1, 12, 14, 8, 8, 2, 13, 4, 6, 9, 2, 1, 11, 7, + 15, 5, 12, 11, 9, 3, 7, 14, 3, 10, 10, 0, 5, 6, 0, 13 + ), + array( + 15, 3, 1, 13, 8, 4, 14, 7, 6, 15, 11, 2, 3, 8, 4, 14, + 9, 12, 7, 0, 2, 1, 13, 10, 12, 6, 0, 9, 5, 11, 10, 5, + 0, 13, 14, 8, 7, 10, 11, 1, 10, 3, 4, 15, 13, 4, 1, 2, + 5, 11, 8, 6, 12, 7, 6, 12, 9, 0, 3, 5, 2, 14, 15, 9 + ), + array( + 10, 13, 0, 7, 9, 0, 14, 9, 6, 3, 3, 4, 15, 6, 5, 10, + 1, 2, 13, 8, 12, 5, 7, 14, 11, 12, 4, 11, 2, 15, 8, 1, + 13, 1, 6, 10, 4, 13, 9, 0, 8, 6, 15, 9, 3, 8, 0, 7, + 11, 4, 1, 15, 2, 14, 12, 3, 5, 11, 10, 5, 14, 2, 7, 12 + ), + array( + 7, 13, 13, 8, 14, 11, 3, 5, 0, 6, 6, 15, 9, 0, 10, 3, + 1, 4, 2, 7, 8, 2, 5, 12, 11, 1, 12, 10, 4, 14, 15, 9, + 10, 3, 6, 15, 9, 0, 0, 6, 12, 10, 11, 1, 7, 13, 13, 8, + 15, 9, 1, 4, 3, 5, 14, 11, 5, 12, 2, 7, 8, 2, 4, 14 + ), + array( + 2, 14, 12, 11, 4, 2, 1, 12, 7, 4, 10, 7, 11, 13, 6, 1, + 8, 5, 5, 0, 3, 15, 15, 10, 13, 3, 0, 9, 14, 8, 9, 6, + 4, 11, 2, 8, 1, 12, 11, 7, 10, 1, 13, 14, 7, 2, 8, 13, + 15, 6, 9, 15, 12, 0, 5, 9, 6, 10, 3, 4, 0, 5, 14, 3 + ), + array( + 12, 10, 1, 15, 10, 4, 15, 2, 9, 7, 2, 12, 6, 9, 8, 5, + 0, 6, 13, 1, 3, 13, 4, 14, 14, 0, 7, 11, 5, 3, 11, 8, + 9, 4, 14, 3, 15, 2, 5, 12, 2, 9, 8, 5, 12, 15, 3, 10, + 7, 11, 0, 14, 4, 1, 10, 7, 1, 6, 13, 0, 11, 8, 6, 13 + ), + array( + 4, 13, 11, 0, 2, 11, 14, 7, 15, 4, 0, 9, 8, 1, 13, 10, + 3, 14, 12, 3, 9, 5, 7, 12, 5, 2, 10, 15, 6, 8, 1, 6, + 1, 6, 4, 11, 11, 13, 13, 8, 12, 1, 3, 4, 7, 10, 14, 7, + 10, 9, 15, 5, 6, 0, 8, 15, 0, 14, 5, 2, 9, 3, 2, 12 + ), + array( + 13, 1, 2, 15, 8, 13, 4, 8, 6, 10, 15, 3, 11, 7, 1, 4, + 10, 12, 9, 5, 3, 6, 14, 11, 5, 0, 0, 14, 12, 9, 7, 2, + 7, 2, 11, 1, 4, 14, 1, 7, 9, 4, 12, 10, 14, 8, 2, 13, + 0, 15, 6, 12, 10, 9, 13, 0, 15, 3, 3, 5, 5, 6, 8, 11 + ) + ); + + $temp = unpack('Na/Nb', $block); + $block = array($temp['a'], $temp['b']); + + // because php does arithmetic right shifts, if the most significant bits are set, right + // shifting those into the correct position will add 1's - not 0's. this will intefere + // with the | operation unless a second & is done. so we isolate these bits and left shift + // them into place. we then & each block with 0x7FFFFFFF to prevennt 1's from being added + // for any other shifts. + $msb = array( + ($block[0] >> 31) & 1, + ($block[1] >> 31) & 1 + ); + $block[0] &= 0x7FFFFFFF; + $block[1] &= 0x7FFFFFFF; + + // we isolate the appropriate bit in the appropriate integer and shift as appropriate. in + // some cases, there are going to be multiple bits in the same integer that need to be shifted + // in the same way. we combine those into one shift operation. + $block = array( + (($block[1] & 0x00000040) << 25) | (($block[1] & 0x00004000) << 16) | + (($block[1] & 0x00400001) << 7) | (($block[1] & 0x40000100) >> 2) | + (($block[0] & 0x00000040) << 21) | (($block[0] & 0x00004000) << 12) | + (($block[0] & 0x00400001) << 3) | (($block[0] & 0x40000100) >> 6) | + (($block[1] & 0x00000010) << 19) | (($block[1] & 0x00001000) << 10) | + (($block[1] & 0x00100000) << 1) | (($block[1] & 0x10000000) >> 8) | + (($block[0] & 0x00000010) << 15) | (($block[0] & 0x00001000) << 6) | + (($block[0] & 0x00100000) >> 3) | (($block[0] & 0x10000000) >> 12) | + (($block[1] & 0x00000004) << 13) | (($block[1] & 0x00000400) << 4) | + (($block[1] & 0x00040000) >> 5) | (($block[1] & 0x04000000) >> 14) | + (($block[0] & 0x00000004) << 9) | ( $block[0] & 0x00000400 ) | + (($block[0] & 0x00040000) >> 9) | (($block[0] & 0x04000000) >> 18) | + (($block[1] & 0x00010000) >> 11) | (($block[1] & 0x01000000) >> 20) | + (($block[0] & 0x00010000) >> 15) | (($block[0] & 0x01000000) >> 24) + , + (($block[1] & 0x00000080) << 24) | (($block[1] & 0x00008000) << 15) | + (($block[1] & 0x00800002) << 6) | (($block[0] & 0x00000080) << 20) | + (($block[0] & 0x00008000) << 11) | (($block[0] & 0x00800002) << 2) | + (($block[1] & 0x00000020) << 18) | (($block[1] & 0x00002000) << 9) | + ( $block[1] & 0x00200000 ) | (($block[1] & 0x20000000) >> 9) | + (($block[0] & 0x00000020) << 14) | (($block[0] & 0x00002000) << 5) | + (($block[0] & 0x00200000) >> 4) | (($block[0] & 0x20000000) >> 13) | + (($block[1] & 0x00000008) << 12) | (($block[1] & 0x00000800) << 3) | + (($block[1] & 0x00080000) >> 6) | (($block[1] & 0x08000000) >> 15) | + (($block[0] & 0x00000008) << 8) | (($block[0] & 0x00000800) >> 1) | + (($block[0] & 0x00080000) >> 10) | (($block[0] & 0x08000000) >> 19) | + (($block[1] & 0x00000200) >> 3) | (($block[0] & 0x00000200) >> 7) | + (($block[1] & 0x00020000) >> 12) | (($block[1] & 0x02000000) >> 21) | + (($block[0] & 0x00020000) >> 16) | (($block[0] & 0x02000000) >> 25) | + ($msb[1] << 28) | ($msb[0] << 24) + ); + + for ($i = 0; $i < 16; $i++) { + // start of "the Feistel (F) function" - see the following URL: + // http://en.wikipedia.org/wiki/Image:Data_Encryption_Standard_InfoBox_Diagram.png + $temp = (($sbox[0][((($block[1] >> 27) & 0x1F) | (($block[1] & 1) << 5)) ^ $this->keys[$mode][$i][0]]) << 28) + | (($sbox[1][(($block[1] & 0x1F800000) >> 23) ^ $this->keys[$mode][$i][1]]) << 24) + | (($sbox[2][(($block[1] & 0x01F80000) >> 19) ^ $this->keys[$mode][$i][2]]) << 20) + | (($sbox[3][(($block[1] & 0x001F8000) >> 15) ^ $this->keys[$mode][$i][3]]) << 16) + | (($sbox[4][(($block[1] & 0x0001F800) >> 11) ^ $this->keys[$mode][$i][4]]) << 12) + | (($sbox[5][(($block[1] & 0x00001F80) >> 7) ^ $this->keys[$mode][$i][5]]) << 8) + | (($sbox[6][(($block[1] & 0x000001F8) >> 3) ^ $this->keys[$mode][$i][6]]) << 4) + | ( $sbox[7][((($block[1] & 0x1F) << 1) | (($block[1] >> 31) & 1)) ^ $this->keys[$mode][$i][7]]); + + $msb = ($temp >> 31) & 1; + $temp &= 0x7FFFFFFF; + $newBlock = (($temp & 0x00010000) << 15) | (($temp & 0x02020120) << 5) + | (($temp & 0x00001800) << 17) | (($temp & 0x01000000) >> 10) + | (($temp & 0x00000008) << 24) | (($temp & 0x00100000) << 6) + | (($temp & 0x00000010) << 21) | (($temp & 0x00008000) << 9) + | (($temp & 0x00000200) << 12) | (($temp & 0x10000000) >> 27) + | (($temp & 0x00000040) << 14) | (($temp & 0x08000000) >> 8) + | (($temp & 0x00004000) << 4) | (($temp & 0x00000002) << 16) + | (($temp & 0x00442000) >> 6) | (($temp & 0x40800000) >> 15) + | (($temp & 0x00000001) << 11) | (($temp & 0x20000000) >> 20) + | (($temp & 0x00080000) >> 13) | (($temp & 0x00000004) << 3) + | (($temp & 0x04000000) >> 22) | (($temp & 0x00000480) >> 7) + | (($temp & 0x00200000) >> 19) | ($msb << 23); + // end of "the Feistel (F) function" - $newBlock is F's output + + $temp = $block[1]; + $block[1] = $block[0] ^ $newBlock; + $block[0] = $temp; + } + + $msb = array( + ($block[0] >> 31) & 1, + ($block[1] >> 31) & 1 + ); + $block[0] &= 0x7FFFFFFF; + $block[1] &= 0x7FFFFFFF; + + $block = array( + (($block[0] & 0x01000004) << 7) | (($block[1] & 0x01000004) << 6) | + (($block[0] & 0x00010000) << 13) | (($block[1] & 0x00010000) << 12) | + (($block[0] & 0x00000100) << 19) | (($block[1] & 0x00000100) << 18) | + (($block[0] & 0x00000001) << 25) | (($block[1] & 0x00000001) << 24) | + (($block[0] & 0x02000008) >> 2) | (($block[1] & 0x02000008) >> 3) | + (($block[0] & 0x00020000) << 4) | (($block[1] & 0x00020000) << 3) | + (($block[0] & 0x00000200) << 10) | (($block[1] & 0x00000200) << 9) | + (($block[0] & 0x00000002) << 16) | (($block[1] & 0x00000002) << 15) | + (($block[0] & 0x04000000) >> 11) | (($block[1] & 0x04000000) >> 12) | + (($block[0] & 0x00040000) >> 5) | (($block[1] & 0x00040000) >> 6) | + (($block[0] & 0x00000400) << 1) | ( $block[1] & 0x00000400 ) | + (($block[0] & 0x08000000) >> 20) | (($block[1] & 0x08000000) >> 21) | + (($block[0] & 0x00080000) >> 14) | (($block[1] & 0x00080000) >> 15) | + (($block[0] & 0x00000800) >> 8) | (($block[1] & 0x00000800) >> 9) + , + (($block[0] & 0x10000040) << 3) | (($block[1] & 0x10000040) << 2) | + (($block[0] & 0x00100000) << 9) | (($block[1] & 0x00100000) << 8) | + (($block[0] & 0x00001000) << 15) | (($block[1] & 0x00001000) << 14) | + (($block[0] & 0x00000010) << 21) | (($block[1] & 0x00000010) << 20) | + (($block[0] & 0x20000080) >> 6) | (($block[1] & 0x20000080) >> 7) | + ( $block[0] & 0x00200000 ) | (($block[1] & 0x00200000) >> 1) | + (($block[0] & 0x00002000) << 6) | (($block[1] & 0x00002000) << 5) | + (($block[0] & 0x00000020) << 12) | (($block[1] & 0x00000020) << 11) | + (($block[0] & 0x40000000) >> 15) | (($block[1] & 0x40000000) >> 16) | + (($block[0] & 0x00400000) >> 9) | (($block[1] & 0x00400000) >> 10) | + (($block[0] & 0x00004000) >> 3) | (($block[1] & 0x00004000) >> 4) | + (($block[0] & 0x00800000) >> 18) | (($block[1] & 0x00800000) >> 19) | + (($block[0] & 0x00008000) >> 12) | (($block[1] & 0x00008000) >> 13) | + ($msb[0] << 7) | ($msb[1] << 6) + ); + + return pack('NN', $block[0], $block[1]); + } + + /** + * Creates the key schedule. + * + * @access private + * @param String $key + * @return Array + */ + function _prepareKey($key) + { + static $shifts = array( // number of key bits shifted per round + 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 + ); + + // pad the key and remove extra characters as appropriate. + $key = str_pad(substr($key, 0, 8), 8, chr(0)); + + $temp = unpack('Na/Nb', $key); + $key = array($temp['a'], $temp['b']); + $msb = array( + ($key[0] >> 31) & 1, + ($key[1] >> 31) & 1 + ); + $key[0] &= 0x7FFFFFFF; + $key[1] &= 0x7FFFFFFF; + + $key = array( + (($key[1] & 0x00000002) << 26) | (($key[1] & 0x00000204) << 17) | + (($key[1] & 0x00020408) << 8) | (($key[1] & 0x02040800) >> 1) | + (($key[0] & 0x00000002) << 22) | (($key[0] & 0x00000204) << 13) | + (($key[0] & 0x00020408) << 4) | (($key[0] & 0x02040800) >> 5) | + (($key[1] & 0x04080000) >> 10) | (($key[0] & 0x04080000) >> 14) | + (($key[1] & 0x08000000) >> 19) | (($key[0] & 0x08000000) >> 23) | + (($key[0] & 0x00000010) >> 1) | (($key[0] & 0x00001000) >> 10) | + (($key[0] & 0x00100000) >> 19) | (($key[0] & 0x10000000) >> 28) + , + (($key[1] & 0x00000080) << 20) | (($key[1] & 0x00008000) << 11) | + (($key[1] & 0x00800000) << 2) | (($key[0] & 0x00000080) << 16) | + (($key[0] & 0x00008000) << 7) | (($key[0] & 0x00800000) >> 2) | + (($key[1] & 0x00000040) << 13) | (($key[1] & 0x00004000) << 4) | + (($key[1] & 0x00400000) >> 5) | (($key[1] & 0x40000000) >> 14) | + (($key[0] & 0x00000040) << 9) | ( $key[0] & 0x00004000 ) | + (($key[0] & 0x00400000) >> 9) | (($key[0] & 0x40000000) >> 18) | + (($key[1] & 0x00000020) << 6) | (($key[1] & 0x00002000) >> 3) | + (($key[1] & 0x00200000) >> 12) | (($key[1] & 0x20000000) >> 21) | + (($key[0] & 0x00000020) << 2) | (($key[0] & 0x00002000) >> 7) | + (($key[0] & 0x00200000) >> 16) | (($key[0] & 0x20000000) >> 25) | + (($key[1] & 0x00000010) >> 1) | (($key[1] & 0x00001000) >> 10) | + (($key[1] & 0x00100000) >> 19) | (($key[1] & 0x10000000) >> 28) | + ($msb[1] << 24) | ($msb[0] << 20) + ); + + $keys = array(); + for ($i = 0; $i < 16; $i++) { + $key[0] <<= $shifts[$i]; + $temp = ($key[0] & 0xF0000000) >> 28; + $key[0] = ($key[0] | $temp) & 0x0FFFFFFF; + + $key[1] <<= $shifts[$i]; + $temp = ($key[1] & 0xF0000000) >> 28; + $key[1] = ($key[1] | $temp) & 0x0FFFFFFF; + + $temp = array( + (($key[1] & 0x00004000) >> 9) | (($key[1] & 0x00000800) >> 7) | + (($key[1] & 0x00020000) >> 14) | (($key[1] & 0x00000010) >> 2) | + (($key[1] & 0x08000000) >> 26) | (($key[1] & 0x00800000) >> 23) + , + (($key[1] & 0x02400000) >> 20) | (($key[1] & 0x00000001) << 4) | + (($key[1] & 0x00002000) >> 10) | (($key[1] & 0x00040000) >> 18) | + (($key[1] & 0x00000080) >> 6) + , + ( $key[1] & 0x00000020 ) | (($key[1] & 0x00000200) >> 5) | + (($key[1] & 0x00010000) >> 13) | (($key[1] & 0x01000000) >> 22) | + (($key[1] & 0x00000004) >> 1) | (($key[1] & 0x00100000) >> 20) + , + (($key[1] & 0x00001000) >> 7) | (($key[1] & 0x00200000) >> 17) | + (($key[1] & 0x00000002) << 2) | (($key[1] & 0x00000100) >> 6) | + (($key[1] & 0x00008000) >> 14) | (($key[1] & 0x04000000) >> 26) + , + (($key[0] & 0x00008000) >> 10) | ( $key[0] & 0x00000010 ) | + (($key[0] & 0x02000000) >> 22) | (($key[0] & 0x00080000) >> 17) | + (($key[0] & 0x00000200) >> 8) | (($key[0] & 0x00000002) >> 1) + , + (($key[0] & 0x04000000) >> 21) | (($key[0] & 0x00010000) >> 12) | + (($key[0] & 0x00000020) >> 2) | (($key[0] & 0x00000800) >> 9) | + (($key[0] & 0x00800000) >> 22) | (($key[0] & 0x00000100) >> 8) + , + (($key[0] & 0x00001000) >> 7) | (($key[0] & 0x00000088) >> 3) | + (($key[0] & 0x00020000) >> 14) | (($key[0] & 0x00000001) << 2) | + (($key[0] & 0x00400000) >> 21) + , + (($key[0] & 0x00000400) >> 5) | (($key[0] & 0x00004000) >> 10) | + (($key[0] & 0x00000040) >> 3) | (($key[0] & 0x00100000) >> 18) | + (($key[0] & 0x08000000) >> 26) | (($key[0] & 0x01000000) >> 24) + ); + + $keys[] = $temp; + } + + $temp = array( + CRYPT_DES_ENCRYPT => $keys, + CRYPT_DES_DECRYPT => array_reverse($keys) + ); + + return $temp; + } +} + +// vim: ts=4:sw=4:et: +// vim6: fdl=1: \ No newline at end of file diff --git a/plugins/OStatus/extlib/Crypt/Hash.php b/plugins/OStatus/extlib/Crypt/Hash.php new file mode 100644 index 000000000..ef3a85802 --- /dev/null +++ b/plugins/OStatus/extlib/Crypt/Hash.php @@ -0,0 +1,816 @@ + + * setKey('abcdefg'); + * + * echo base64_encode($hash->hash('abcdefg')); + * ?> + * + * + * LICENSE: This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * @category Crypt + * @package Crypt_Hash + * @author Jim Wigginton + * @copyright MMVII Jim Wigginton + * @license http://www.gnu.org/licenses/lgpl.txt + * @version $Id: Hash.php,v 1.6 2009/11/23 23:37:07 terrafrost Exp $ + * @link http://phpseclib.sourceforge.net + */ + +/**#@+ + * @access private + * @see Crypt_Hash::Crypt_Hash() + */ +/** + * Toggles the internal implementation + */ +define('CRYPT_HASH_MODE_INTERNAL', 1); +/** + * Toggles the mhash() implementation, which has been deprecated on PHP 5.3.0+. + */ +define('CRYPT_HASH_MODE_MHASH', 2); +/** + * Toggles the hash() implementation, which works on PHP 5.1.2+. + */ +define('CRYPT_HASH_MODE_HASH', 3); +/**#@-*/ + +/** + * Pure-PHP implementations of keyed-hash message authentication codes (HMACs) and various cryptographic hashing functions. + * + * @author Jim Wigginton + * @version 0.1.0 + * @access public + * @package Crypt_Hash + */ +class Crypt_Hash { + /** + * Byte-length of compression blocks / key (Internal HMAC) + * + * @see Crypt_Hash::setAlgorithm() + * @var Integer + * @access private + */ + var $b; + + /** + * Byte-length of hash output (Internal HMAC) + * + * @see Crypt_Hash::setHash() + * @var Integer + * @access private + */ + var $l = false; + + /** + * Hash Algorithm + * + * @see Crypt_Hash::setHash() + * @var String + * @access private + */ + var $hash; + + /** + * Key + * + * @see Crypt_Hash::setKey() + * @var String + * @access private + */ + var $key = ''; + + /** + * Outer XOR (Internal HMAC) + * + * @see Crypt_Hash::setKey() + * @var String + * @access private + */ + var $opad; + + /** + * Inner XOR (Internal HMAC) + * + * @see Crypt_Hash::setKey() + * @var String + * @access private + */ + var $ipad; + + /** + * Default Constructor. + * + * @param optional String $hash + * @return Crypt_Hash + * @access public + */ + function Crypt_Hash($hash = 'sha1') + { + if ( !defined('CRYPT_HASH_MODE') ) { + switch (true) { + case extension_loaded('hash'): + define('CRYPT_HASH_MODE', CRYPT_HASH_MODE_HASH); + break; + case extension_loaded('mhash'): + define('CRYPT_HASH_MODE', CRYPT_HASH_MODE_MHASH); + break; + default: + define('CRYPT_HASH_MODE', CRYPT_HASH_MODE_INTERNAL); + } + } + + $this->setHash($hash); + } + + /** + * Sets the key for HMACs + * + * Keys can be of any length. + * + * @access public + * @param String $key + */ + function setKey($key) + { + $this->key = $key; + } + + /** + * Sets the hash function. + * + * @access public + * @param String $hash + */ + function setHash($hash) + { + switch ($hash) { + case 'md5-96': + case 'sha1-96': + $this->l = 12; // 96 / 8 = 12 + break; + case 'md2': + case 'md5': + $this->l = 16; + break; + case 'sha1': + $this->l = 20; + break; + case 'sha256': + $this->l = 32; + break; + case 'sha384': + $this->l = 48; + break; + case 'sha512': + $this->l = 64; + } + + switch ($hash) { + case 'md2': + $mode = CRYPT_HASH_MODE_INTERNAL; + break; + case 'sha384': + case 'sha512': + $mode = CRYPT_HASH_MODE == CRYPT_HASH_MODE_MHASH ? CRYPT_HASH_MODE_INTERNAL : CRYPT_HASH_MODE; + break; + default: + $mode = CRYPT_HASH_MODE; + } + + switch ( $mode ) { + case CRYPT_HASH_MODE_MHASH: + switch ($hash) { + case 'md5': + case 'md5-96': + $this->hash = MHASH_MD5; + break; + case 'sha256': + $this->hash = MHASH_SHA256; + break; + case 'sha1': + case 'sha1-96': + default: + $this->hash = MHASH_SHA1; + } + return; + case CRYPT_HASH_MODE_HASH: + switch ($hash) { + case 'md5': + case 'md5-96': + $this->hash = 'md5'; + return; + case 'sha256': + case 'sha384': + case 'sha512': + $this->hash = $hash; + return; + case 'sha1': + case 'sha1-96': + default: + $this->hash = 'sha1'; + } + return; + } + + switch ($hash) { + case 'md2': + $this->b = 16; + $this->hash = array($this, '_md2'); + break; + case 'md5': + case 'md5-96': + $this->b = 64; + $this->hash = array($this, '_md5'); + break; + case 'sha256': + $this->b = 64; + $this->hash = array($this, '_sha256'); + break; + case 'sha384': + case 'sha512': + $this->b = 128; + $this->hash = array($this, '_sha512'); + break; + case 'sha1': + case 'sha1-96': + default: + $this->b = 64; + $this->hash = array($this, '_sha1'); + } + + $this->ipad = str_repeat(chr(0x36), $this->b); + $this->opad = str_repeat(chr(0x5C), $this->b); + } + + /** + * Compute the HMAC. + * + * @access public + * @param String $text + * @return String + */ + function hash($text) + { + $mode = is_array($this->hash) ? CRYPT_HASH_MODE_INTERNAL : CRYPT_HASH_MODE; + + if (!empty($this->key)) { + switch ( $mode ) { + case CRYPT_HASH_MODE_MHASH: + $output = mhash($this->hash, $text, $this->key); + break; + case CRYPT_HASH_MODE_HASH: + $output = hash_hmac($this->hash, $text, $this->key, true); + break; + case CRYPT_HASH_MODE_INTERNAL: + /* "Applications that use keys longer than B bytes will first hash the key using H and then use the + resultant L byte string as the actual key to HMAC." + + -- http://tools.ietf.org/html/rfc2104#section-2 */ + $key = strlen($this->key) > $this->b ? call_user_func($this->$hash, $this->key) : $this->key; + + $key = str_pad($key, $this->b, chr(0)); // step 1 + $temp = $this->ipad ^ $key; // step 2 + $temp .= $text; // step 3 + $temp = call_user_func($this->hash, $temp); // step 4 + $output = $this->opad ^ $key; // step 5 + $output.= $temp; // step 6 + $output = call_user_func($this->hash, $output); // step 7 + } + } else { + switch ( $mode ) { + case CRYPT_HASH_MODE_MHASH: + $output = mhash($this->hash, $text); + break; + case CRYPT_HASH_MODE_HASH: + $output = hash($this->hash, $text, true); + break; + case CRYPT_HASH_MODE_INTERNAL: + $output = call_user_func($this->hash, $text); + } + } + + return substr($output, 0, $this->l); + } + + /** + * Returns the hash length (in bytes) + * + * @access private + * @return Integer + */ + function getLength() + { + return $this->l; + } + + /** + * Wrapper for MD5 + * + * @access private + * @param String $text + */ + function _md5($m) + { + return pack('H*', md5($m)); + } + + /** + * Wrapper for SHA1 + * + * @access private + * @param String $text + */ + function _sha1($m) + { + return pack('H*', sha1($m)); + } + + /** + * Pure-PHP implementation of MD2 + * + * See {@link http://tools.ietf.org/html/rfc1319 RFC1319}. + * + * @access private + * @param String $text + */ + function _md2($m) + { + static $s = array( + 41, 46, 67, 201, 162, 216, 124, 1, 61, 54, 84, 161, 236, 240, 6, + 19, 98, 167, 5, 243, 192, 199, 115, 140, 152, 147, 43, 217, 188, + 76, 130, 202, 30, 155, 87, 60, 253, 212, 224, 22, 103, 66, 111, 24, + 138, 23, 229, 18, 190, 78, 196, 214, 218, 158, 222, 73, 160, 251, + 245, 142, 187, 47, 238, 122, 169, 104, 121, 145, 21, 178, 7, 63, + 148, 194, 16, 137, 11, 34, 95, 33, 128, 127, 93, 154, 90, 144, 50, + 39, 53, 62, 204, 231, 191, 247, 151, 3, 255, 25, 48, 179, 72, 165, + 181, 209, 215, 94, 146, 42, 172, 86, 170, 198, 79, 184, 56, 210, + 150, 164, 125, 182, 118, 252, 107, 226, 156, 116, 4, 241, 69, 157, + 112, 89, 100, 113, 135, 32, 134, 91, 207, 101, 230, 45, 168, 2, 27, + 96, 37, 173, 174, 176, 185, 246, 28, 70, 97, 105, 52, 64, 126, 15, + 85, 71, 163, 35, 221, 81, 175, 58, 195, 92, 249, 206, 186, 197, + 234, 38, 44, 83, 13, 110, 133, 40, 132, 9, 211, 223, 205, 244, 65, + 129, 77, 82, 106, 220, 55, 200, 108, 193, 171, 250, 36, 225, 123, + 8, 12, 189, 177, 74, 120, 136, 149, 139, 227, 99, 232, 109, 233, + 203, 213, 254, 59, 0, 29, 57, 242, 239, 183, 14, 102, 88, 208, 228, + 166, 119, 114, 248, 235, 117, 75, 10, 49, 68, 80, 180, 143, 237, + 31, 26, 219, 153, 141, 51, 159, 17, 131, 20 + ); + + // Step 1. Append Padding Bytes + $pad = 16 - (strlen($m) & 0xF); + $m.= str_repeat(chr($pad), $pad); + + $length = strlen($m); + + // Step 2. Append Checksum + $c = str_repeat(chr(0), 16); + $l = chr(0); + for ($i = 0; $i < $length; $i+= 16) { + for ($j = 0; $j < 16; $j++) { + $c[$j] = chr($s[ord($m[$i + $j] ^ $l)]); + $l = $c[$j]; + } + } + $m.= $c; + + $length+= 16; + + // Step 3. Initialize MD Buffer + $x = str_repeat(chr(0), 48); + + // Step 4. Process Message in 16-Byte Blocks + for ($i = 0; $i < $length; $i+= 16) { + for ($j = 0; $j < 16; $j++) { + $x[$j + 16] = $m[$i + $j]; + $x[$j + 32] = $x[$j + 16] ^ $x[$j]; + } + $t = chr(0); + for ($j = 0; $j < 18; $j++) { + for ($k = 0; $k < 48; $k++) { + $x[$k] = $t = $x[$k] ^ chr($s[ord($t)]); + //$t = $x[$k] = $x[$k] ^ chr($s[ord($t)]); + } + $t = chr(ord($t) + $j); + } + } + + // Step 5. Output + return substr($x, 0, 16); + } + + /** + * Pure-PHP implementation of SHA256 + * + * See {@link http://en.wikipedia.org/wiki/SHA_hash_functions#SHA-256_.28a_SHA-2_variant.29_pseudocode SHA-256 (a SHA-2 variant) pseudocode - Wikipedia}. + * + * @access private + * @param String $text + */ + function _sha256($m) + { + if (extension_loaded('suhosin')) { + return pack('H*', sha256($m)); + } + + // Initialize variables + $hash = array( + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 + ); + // Initialize table of round constants + // (first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311) + static $k = array( + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + ); + + // Pre-processing + $length = strlen($m); + // to round to nearest 56 mod 64, we'll add 64 - (length + (64 - 56)) % 64 + $m.= str_repeat(chr(0), 64 - (($length + 8) & 0x3F)); + $m[$length] = chr(0x80); + // we don't support hashing strings 512MB long + $m.= pack('N2', 0, $length << 3); + + // Process the message in successive 512-bit chunks + $chunks = str_split($m, 64); + foreach ($chunks as $chunk) { + $w = array(); + for ($i = 0; $i < 16; $i++) { + extract(unpack('Ntemp', $this->_string_shift($chunk, 4))); + $w[] = $temp; + } + + // Extend the sixteen 32-bit words into sixty-four 32-bit words + for ($i = 16; $i < 64; $i++) { + $s0 = $this->_rightRotate($w[$i - 15], 7) ^ + $this->_rightRotate($w[$i - 15], 18) ^ + $this->_rightShift( $w[$i - 15], 3); + $s1 = $this->_rightRotate($w[$i - 2], 17) ^ + $this->_rightRotate($w[$i - 2], 19) ^ + $this->_rightShift( $w[$i - 2], 10); + $w[$i] = $this->_add($w[$i - 16], $s0, $w[$i - 7], $s1); + + } + + // Initialize hash value for this chunk + list($a, $b, $c, $d, $e, $f, $g, $h) = $hash; + + // Main loop + for ($i = 0; $i < 64; $i++) { + $s0 = $this->_rightRotate($a, 2) ^ + $this->_rightRotate($a, 13) ^ + $this->_rightRotate($a, 22); + $maj = ($a & $b) ^ + ($a & $c) ^ + ($b & $c); + $t2 = $this->_add($s0, $maj); + + $s1 = $this->_rightRotate($e, 6) ^ + $this->_rightRotate($e, 11) ^ + $this->_rightRotate($e, 25); + $ch = ($e & $f) ^ + ($this->_not($e) & $g); + $t1 = $this->_add($h, $s1, $ch, $k[$i], $w[$i]); + + $h = $g; + $g = $f; + $f = $e; + $e = $this->_add($d, $t1); + $d = $c; + $c = $b; + $b = $a; + $a = $this->_add($t1, $t2); + } + + // Add this chunk's hash to result so far + $hash = array( + $this->_add($hash[0], $a), + $this->_add($hash[1], $b), + $this->_add($hash[2], $c), + $this->_add($hash[3], $d), + $this->_add($hash[4], $e), + $this->_add($hash[5], $f), + $this->_add($hash[6], $g), + $this->_add($hash[7], $h) + ); + } + + // Produce the final hash value (big-endian) + return pack('N8', $hash[0], $hash[1], $hash[2], $hash[3], $hash[4], $hash[5], $hash[6], $hash[7]); + } + + /** + * Pure-PHP implementation of SHA384 and SHA512 + * + * @access private + * @param String $text + */ + function _sha512($m) + { + if (!class_exists('Math_BigInteger')) { + require_once('Math/BigInteger.php'); + } + + static $init384, $init512, $k; + + if (!isset($k)) { + // Initialize variables + $init384 = array( // initial values for SHA384 + 'cbbb9d5dc1059ed8', '629a292a367cd507', '9159015a3070dd17', '152fecd8f70e5939', + '67332667ffc00b31', '8eb44a8768581511', 'db0c2e0d64f98fa7', '47b5481dbefa4fa4' + ); + $init512 = array( // initial values for SHA512 + '6a09e667f3bcc908', 'bb67ae8584caa73b', '3c6ef372fe94f82b', 'a54ff53a5f1d36f1', + '510e527fade682d1', '9b05688c2b3e6c1f', '1f83d9abfb41bd6b', '5be0cd19137e2179' + ); + + for ($i = 0; $i < 8; $i++) { + $init384[$i] = new Math_BigInteger($init384[$i], 16); + $init384[$i]->setPrecision(64); + $init512[$i] = new Math_BigInteger($init512[$i], 16); + $init512[$i]->setPrecision(64); + } + + // Initialize table of round constants + // (first 64 bits of the fractional parts of the cube roots of the first 80 primes 2..409) + $k = array( + '428a2f98d728ae22', '7137449123ef65cd', 'b5c0fbcfec4d3b2f', 'e9b5dba58189dbbc', + '3956c25bf348b538', '59f111f1b605d019', '923f82a4af194f9b', 'ab1c5ed5da6d8118', + 'd807aa98a3030242', '12835b0145706fbe', '243185be4ee4b28c', '550c7dc3d5ffb4e2', + '72be5d74f27b896f', '80deb1fe3b1696b1', '9bdc06a725c71235', 'c19bf174cf692694', + 'e49b69c19ef14ad2', 'efbe4786384f25e3', '0fc19dc68b8cd5b5', '240ca1cc77ac9c65', + '2de92c6f592b0275', '4a7484aa6ea6e483', '5cb0a9dcbd41fbd4', '76f988da831153b5', + '983e5152ee66dfab', 'a831c66d2db43210', 'b00327c898fb213f', 'bf597fc7beef0ee4', + 'c6e00bf33da88fc2', 'd5a79147930aa725', '06ca6351e003826f', '142929670a0e6e70', + '27b70a8546d22ffc', '2e1b21385c26c926', '4d2c6dfc5ac42aed', '53380d139d95b3df', + '650a73548baf63de', '766a0abb3c77b2a8', '81c2c92e47edaee6', '92722c851482353b', + 'a2bfe8a14cf10364', 'a81a664bbc423001', 'c24b8b70d0f89791', 'c76c51a30654be30', + 'd192e819d6ef5218', 'd69906245565a910', 'f40e35855771202a', '106aa07032bbd1b8', + '19a4c116b8d2d0c8', '1e376c085141ab53', '2748774cdf8eeb99', '34b0bcb5e19b48a8', + '391c0cb3c5c95a63', '4ed8aa4ae3418acb', '5b9cca4f7763e373', '682e6ff3d6b2b8a3', + '748f82ee5defb2fc', '78a5636f43172f60', '84c87814a1f0ab72', '8cc702081a6439ec', + '90befffa23631e28', 'a4506cebde82bde9', 'bef9a3f7b2c67915', 'c67178f2e372532b', + 'ca273eceea26619c', 'd186b8c721c0c207', 'eada7dd6cde0eb1e', 'f57d4f7fee6ed178', + '06f067aa72176fba', '0a637dc5a2c898a6', '113f9804bef90dae', '1b710b35131c471b', + '28db77f523047d84', '32caab7b40c72493', '3c9ebe0a15c9bebc', '431d67c49c100d4c', + '4cc5d4becb3e42b6', '597f299cfc657e2a', '5fcb6fab3ad6faec', '6c44198c4a475817' + ); + + for ($i = 0; $i < 80; $i++) { + $k[$i] = new Math_BigInteger($k[$i], 16); + } + } + + $hash = $this->l == 48 ? $init384 : $init512; + + // Pre-processing + $length = strlen($m); + // to round to nearest 112 mod 128, we'll add 128 - (length + (128 - 112)) % 128 + $m.= str_repeat(chr(0), 128 - (($length + 16) & 0x7F)); + $m[$length] = chr(0x80); + // we don't support hashing strings 512MB long + $m.= pack('N4', 0, 0, 0, $length << 3); + + // Process the message in successive 1024-bit chunks + $chunks = str_split($m, 128); + foreach ($chunks as $chunk) { + $w = array(); + for ($i = 0; $i < 16; $i++) { + $temp = new Math_BigInteger($this->_string_shift($chunk, 8), 256); + $temp->setPrecision(64); + $w[] = $temp; + } + + // Extend the sixteen 32-bit words into eighty 32-bit words + for ($i = 16; $i < 80; $i++) { + $temp = array( + $w[$i - 15]->bitwise_rightRotate(1), + $w[$i - 15]->bitwise_rightRotate(8), + $w[$i - 15]->bitwise_rightShift(7) + ); + $s0 = $temp[0]->bitwise_xor($temp[1]); + $s0 = $s0->bitwise_xor($temp[2]); + $temp = array( + $w[$i - 2]->bitwise_rightRotate(19), + $w[$i - 2]->bitwise_rightRotate(61), + $w[$i - 2]->bitwise_rightShift(6) + ); + $s1 = $temp[0]->bitwise_xor($temp[1]); + $s1 = $s1->bitwise_xor($temp[2]); + $w[$i] = $w[$i - 16]->copy(); + $w[$i] = $w[$i]->add($s0); + $w[$i] = $w[$i]->add($w[$i - 7]); + $w[$i] = $w[$i]->add($s1); + } + + // Initialize hash value for this chunk + $a = $hash[0]->copy(); + $b = $hash[1]->copy(); + $c = $hash[2]->copy(); + $d = $hash[3]->copy(); + $e = $hash[4]->copy(); + $f = $hash[5]->copy(); + $g = $hash[6]->copy(); + $h = $hash[7]->copy(); + + // Main loop + for ($i = 0; $i < 80; $i++) { + $temp = array( + $a->bitwise_rightRotate(28), + $a->bitwise_rightRotate(34), + $a->bitwise_rightRotate(39) + ); + $s0 = $temp[0]->bitwise_xor($temp[1]); + $s0 = $s0->bitwise_xor($temp[2]); + $temp = array( + $a->bitwise_and($b), + $a->bitwise_and($c), + $b->bitwise_and($c) + ); + $maj = $temp[0]->bitwise_xor($temp[1]); + $maj = $maj->bitwise_xor($temp[2]); + $t2 = $s0->add($maj); + + $temp = array( + $e->bitwise_rightRotate(14), + $e->bitwise_rightRotate(18), + $e->bitwise_rightRotate(41) + ); + $s1 = $temp[0]->bitwise_xor($temp[1]); + $s1 = $s1->bitwise_xor($temp[2]); + $temp = array( + $e->bitwise_and($f), + $g->bitwise_and($e->bitwise_not()) + ); + $ch = $temp[0]->bitwise_xor($temp[1]); + $t1 = $h->add($s1); + $t1 = $t1->add($ch); + $t1 = $t1->add($k[$i]); + $t1 = $t1->add($w[$i]); + + $h = $g->copy(); + $g = $f->copy(); + $f = $e->copy(); + $e = $d->add($t1); + $d = $c->copy(); + $c = $b->copy(); + $b = $a->copy(); + $a = $t1->add($t2); + } + + // Add this chunk's hash to result so far + $hash = array( + $hash[0]->add($a), + $hash[1]->add($b), + $hash[2]->add($c), + $hash[3]->add($d), + $hash[4]->add($e), + $hash[5]->add($f), + $hash[6]->add($g), + $hash[7]->add($h) + ); + } + + // Produce the final hash value (big-endian) + // (Crypt_Hash::hash() trims the output for hashes but not for HMACs. as such, we trim the output here) + $temp = $hash[0]->toBytes() . $hash[1]->toBytes() . $hash[2]->toBytes() . $hash[3]->toBytes() . + $hash[4]->toBytes() . $hash[5]->toBytes(); + if ($this->l != 48) { + $temp.= $hash[6]->toBytes() . $hash[7]->toBytes(); + } + + return $temp; + } + + /** + * Right Rotate + * + * @access private + * @param Integer $int + * @param Integer $amt + * @see _sha256() + * @return Integer + */ + function _rightRotate($int, $amt) + { + $invamt = 32 - $amt; + $mask = (1 << $invamt) - 1; + return (($int << $invamt) & 0xFFFFFFFF) | (($int >> $amt) & $mask); + } + + /** + * Right Shift + * + * @access private + * @param Integer $int + * @param Integer $amt + * @see _sha256() + * @return Integer + */ + function _rightShift($int, $amt) + { + $mask = (1 << (32 - $amt)) - 1; + return ($int >> $amt) & $mask; + } + + /** + * Not + * + * @access private + * @param Integer $int + * @see _sha256() + * @return Integer + */ + function _not($int) + { + return ~$int & 0xFFFFFFFF; + } + + /** + * Add + * + * _sha256() adds multiple unsigned 32-bit integers. Since PHP doesn't support unsigned integers and since the + * possibility of overflow exists, care has to be taken. Math_BigInteger() could be used but this should be faster. + * + * @param String $string + * @param optional Integer $index + * @return String + * @see _sha256() + * @access private + */ + function _add() + { + static $mod; + if (!isset($mod)) { + $mod = pow(2, 32); + } + + $result = 0; + $arguments = func_get_args(); + foreach ($arguments as $argument) { + $result+= $argument < 0 ? ($argument & 0x7FFFFFFF) + 0x80000000 : $argument; + } + + return fmod($result, $mod); + } + + /** + * String Shift + * + * Inspired by array_shift + * + * @param String $string + * @param optional Integer $index + * @return String + * @access private + */ + function _string_shift(&$string, $index = 1) + { + $substr = substr($string, 0, $index); + $string = substr($string, $index); + return $substr; + } +} \ No newline at end of file diff --git a/plugins/OStatus/extlib/Crypt/RC4.php b/plugins/OStatus/extlib/Crypt/RC4.php new file mode 100644 index 000000000..6f82b2413 --- /dev/null +++ b/plugins/OStatus/extlib/Crypt/RC4.php @@ -0,0 +1,493 @@ + + * setKey('abcdefgh'); + * + * $size = 10 * 1024; + * $plaintext = ''; + * for ($i = 0; $i < $size; $i++) { + * $plaintext.= 'a'; + * } + * + * echo $rc4->decrypt($rc4->encrypt($plaintext)); + * ?> + * + * + * LICENSE: This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * @category Crypt + * @package Crypt_RC4 + * @author Jim Wigginton + * @copyright MMVII Jim Wigginton + * @license http://www.gnu.org/licenses/lgpl.txt + * @version $Id: RC4.php,v 1.8 2009/06/09 04:00:38 terrafrost Exp $ + * @link http://phpseclib.sourceforge.net + */ + +/**#@+ + * @access private + * @see Crypt_RC4::Crypt_RC4() + */ +/** + * Toggles the internal implementation + */ +define('CRYPT_RC4_MODE_INTERNAL', 1); +/** + * Toggles the mcrypt implementation + */ +define('CRYPT_RC4_MODE_MCRYPT', 2); +/**#@-*/ + +/**#@+ + * @access private + * @see Crypt_RC4::_crypt() + */ +define('CRYPT_RC4_ENCRYPT', 0); +define('CRYPT_RC4_DECRYPT', 1); +/**#@-*/ + +/** + * Pure-PHP implementation of RC4. + * + * @author Jim Wigginton + * @version 0.1.0 + * @access public + * @package Crypt_RC4 + */ +class Crypt_RC4 { + /** + * The Key + * + * @see Crypt_RC4::setKey() + * @var String + * @access private + */ + var $key = "\0"; + + /** + * The Key Stream for encryption + * + * If CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT, this will be equal to the mcrypt object + * + * @see Crypt_RC4::setKey() + * @var Array + * @access private + */ + var $encryptStream = false; + + /** + * The Key Stream for decryption + * + * If CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT, this will be equal to the mcrypt object + * + * @see Crypt_RC4::setKey() + * @var Array + * @access private + */ + var $decryptStream = false; + + /** + * The $i and $j indexes for encryption + * + * @see Crypt_RC4::_crypt() + * @var Integer + * @access private + */ + var $encryptIndex = 0; + + /** + * The $i and $j indexes for decryption + * + * @see Crypt_RC4::_crypt() + * @var Integer + * @access private + */ + var $decryptIndex = 0; + + /** + * MCrypt parameters + * + * @see Crypt_RC4::setMCrypt() + * @var Array + * @access private + */ + var $mcrypt = array('', ''); + + /** + * The Encryption Algorithm + * + * Only used if CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT. Only possible values are MCRYPT_RC4 or MCRYPT_ARCFOUR. + * + * @see Crypt_RC4::Crypt_RC4() + * @var Integer + * @access private + */ + var $mode; + + /** + * Default Constructor. + * + * Determines whether or not the mcrypt extension should be used. + * + * @param optional Integer $mode + * @return Crypt_RC4 + * @access public + */ + function Crypt_RC4() + { + if ( !defined('CRYPT_RC4_MODE') ) { + switch (true) { + case extension_loaded('mcrypt') && (defined('MCRYPT_ARCFOUR') || defined('MCRYPT_RC4')): + // i'd check to see if rc4 was supported, by doing in_array('arcfour', mcrypt_list_algorithms('')), + // but since that can be changed after the object has been created, there doesn't seem to be + // a lot of point... + define('CRYPT_RC4_MODE', CRYPT_RC4_MODE_MCRYPT); + break; + default: + define('CRYPT_RC4_MODE', CRYPT_RC4_MODE_INTERNAL); + } + } + + switch ( CRYPT_RC4_MODE ) { + case CRYPT_RC4_MODE_MCRYPT: + switch (true) { + case defined('MCRYPT_ARCFOUR'): + $this->mode = MCRYPT_ARCFOUR; + break; + case defined('MCRYPT_RC4'); + $this->mode = MCRYPT_RC4; + } + } + } + + /** + * Sets the key. + * + * Keys can be between 1 and 256 bytes long. If they are longer then 256 bytes, the first 256 bytes will + * be used. If no key is explicitly set, it'll be assumed to be a single null byte. + * + * @access public + * @param String $key + */ + function setKey($key) + { + $this->key = $key; + + if ( CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT ) { + return; + } + + $keyLength = strlen($key); + $keyStream = array(); + for ($i = 0; $i < 256; $i++) { + $keyStream[$i] = $i; + } + $j = 0; + for ($i = 0; $i < 256; $i++) { + $j = ($j + $keyStream[$i] + ord($key[$i % $keyLength])) & 255; + $temp = $keyStream[$i]; + $keyStream[$i] = $keyStream[$j]; + $keyStream[$j] = $temp; + } + + $this->encryptIndex = $this->decryptIndex = array(0, 0); + $this->encryptStream = $this->decryptStream = $keyStream; + } + + /** + * Dummy function. + * + * Some protocols, such as WEP, prepend an "initialization vector" to the key, effectively creating a new key [1]. + * If you need to use an initialization vector in this manner, feel free to prepend it to the key, yourself, before + * calling setKey(). + * + * [1] WEP's initialization vectors (IV's) are used in a somewhat insecure way. Since, in that protocol, + * the IV's are relatively easy to predict, an attack described by + * {@link http://www.drizzle.com/~aboba/IEEE/rc4_ksaproc.pdf Scott Fluhrer, Itsik Mantin, and Adi Shamir} + * can be used to quickly guess at the rest of the key. The following links elaborate: + * + * {@link http://www.rsa.com/rsalabs/node.asp?id=2009 http://www.rsa.com/rsalabs/node.asp?id=2009} + * {@link http://en.wikipedia.org/wiki/Related_key_attack http://en.wikipedia.org/wiki/Related_key_attack} + * + * @param String $iv + * @see Crypt_RC4::setKey() + * @access public + */ + function setIV($iv) + { + } + + /** + * Sets MCrypt parameters. (optional) + * + * If MCrypt is being used, empty strings will be used, unless otherwise specified. + * + * @link http://php.net/function.mcrypt-module-open#function.mcrypt-module-open + * @access public + * @param optional Integer $algorithm_directory + * @param optional Integer $mode_directory + */ + function setMCrypt($algorithm_directory = '', $mode_directory = '') + { + if ( CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT ) { + $this->mcrypt = array($algorithm_directory, $mode_directory); + $this->_closeMCrypt(); + } + } + + /** + * Encrypts a message. + * + * @see Crypt_RC4::_crypt() + * @access public + * @param String $plaintext + */ + function encrypt($plaintext) + { + return $this->_crypt($plaintext, CRYPT_RC4_ENCRYPT); + } + + /** + * Decrypts a message. + * + * $this->decrypt($this->encrypt($plaintext)) == $this->encrypt($this->encrypt($plaintext)). + * Atleast if the continuous buffer is disabled. + * + * @see Crypt_RC4::_crypt() + * @access public + * @param String $ciphertext + */ + function decrypt($ciphertext) + { + return $this->_crypt($ciphertext, CRYPT_RC4_DECRYPT); + } + + /** + * Encrypts or decrypts a message. + * + * @see Crypt_RC4::encrypt() + * @see Crypt_RC4::decrypt() + * @access private + * @param String $text + * @param Integer $mode + */ + function _crypt($text, $mode) + { + if ( CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT ) { + $keyStream = $mode == CRYPT_RC4_ENCRYPT ? 'encryptStream' : 'decryptStream'; + + if ($this->$keyStream === false) { + $this->$keyStream = mcrypt_module_open($this->mode, $this->mcrypt[0], MCRYPT_MODE_STREAM, $this->mcrypt[1]); + mcrypt_generic_init($this->$keyStream, $this->key, ''); + } else if (!$this->continuousBuffer) { + mcrypt_generic_init($this->$keyStream, $this->key, ''); + } + $newText = mcrypt_generic($this->$keyStream, $text); + if (!$this->continuousBuffer) { + mcrypt_generic_deinit($this->$keyStream); + } + + return $newText; + } + + if ($this->encryptStream === false) { + $this->setKey($this->key); + } + + switch ($mode) { + case CRYPT_RC4_ENCRYPT: + $keyStream = $this->encryptStream; + list($i, $j) = $this->encryptIndex; + break; + case CRYPT_RC4_DECRYPT: + $keyStream = $this->decryptStream; + list($i, $j) = $this->decryptIndex; + } + + $newText = ''; + for ($k = 0; $k < strlen($text); $k++) { + $i = ($i + 1) & 255; + $j = ($j + $keyStream[$i]) & 255; + $temp = $keyStream[$i]; + $keyStream[$i] = $keyStream[$j]; + $keyStream[$j] = $temp; + $temp = $keyStream[($keyStream[$i] + $keyStream[$j]) & 255]; + $newText.= chr(ord($text[$k]) ^ $temp); + } + + if ($this->continuousBuffer) { + switch ($mode) { + case CRYPT_RC4_ENCRYPT: + $this->encryptStream = $keyStream; + $this->encryptIndex = array($i, $j); + break; + case CRYPT_RC4_DECRYPT: + $this->decryptStream = $keyStream; + $this->decryptIndex = array($i, $j); + } + } + + return $newText; + } + + /** + * Treat consecutive "packets" as if they are a continuous buffer. + * + * Say you have a 16-byte plaintext $plaintext. Using the default behavior, the two following code snippets + * will yield different outputs: + * + * + * echo $rc4->encrypt(substr($plaintext, 0, 8)); + * echo $rc4->encrypt(substr($plaintext, 8, 8)); + * + * + * echo $rc4->encrypt($plaintext); + * + * + * The solution is to enable the continuous buffer. Although this will resolve the above discrepancy, it creates + * another, as demonstrated with the following: + * + * + * $rc4->encrypt(substr($plaintext, 0, 8)); + * echo $rc4->decrypt($des->encrypt(substr($plaintext, 8, 8))); + * + * + * echo $rc4->decrypt($des->encrypt(substr($plaintext, 8, 8))); + * + * + * With the continuous buffer disabled, these would yield the same output. With it enabled, they yield different + * outputs. The reason is due to the fact that the initialization vector's change after every encryption / + * decryption round when the continuous buffer is enabled. When it's disabled, they remain constant. + * + * Put another way, when the continuous buffer is enabled, the state of the Crypt_DES() object changes after each + * encryption / decryption round, whereas otherwise, it'd remain constant. For this reason, it's recommended that + * continuous buffers not be used. They do offer better security and are, in fact, sometimes required (SSH uses them), + * however, they are also less intuitive and more likely to cause you problems. + * + * @see Crypt_RC4::disableContinuousBuffer() + * @access public + */ + function enableContinuousBuffer() + { + $this->continuousBuffer = true; + } + + /** + * Treat consecutive packets as if they are a discontinuous buffer. + * + * The default behavior. + * + * @see Crypt_RC4::enableContinuousBuffer() + * @access public + */ + function disableContinuousBuffer() + { + if ( CRYPT_RC4_MODE == CRYPT_RC4_MODE_INTERNAL ) { + $this->encryptIndex = $this->decryptIndex = array(0, 0); + $this->setKey($this->key); + } + + $this->continuousBuffer = false; + } + + /** + * Dummy function. + * + * Since RC4 is a stream cipher and not a block cipher, no padding is necessary. The only reason this function is + * included is so that you can switch between a block cipher and a stream cipher transparently. + * + * @see Crypt_RC4::disablePadding() + * @access public + */ + function enablePadding() + { + } + + /** + * Dummy function. + * + * @see Crypt_RC4::enablePadding() + * @access public + */ + function disablePadding() + { + } + + /** + * Class destructor. + * + * Will be called, automatically, if you're using PHP5. If you're using PHP4, call it yourself. Only really + * needs to be called if mcrypt is being used. + * + * @access public + */ + function __destruct() + { + if ( CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT ) { + $this->_closeMCrypt(); + } + } + + /** + * Properly close the MCrypt objects. + * + * @access prviate + */ + function _closeMCrypt() + { + if ( $this->encryptStream !== false ) { + if ( $this->continuousBuffer ) { + mcrypt_generic_deinit($this->encryptStream); + } + + mcrypt_module_close($this->encryptStream); + + $this->encryptStream = false; + } + + if ( $this->decryptStream !== false ) { + if ( $this->continuousBuffer ) { + mcrypt_generic_deinit($this->decryptStream); + } + + mcrypt_module_close($this->decryptStream); + + $this->decryptStream = false; + } + } +} \ No newline at end of file diff --git a/plugins/OStatus/extlib/Crypt/RSA.php b/plugins/OStatus/extlib/Crypt/RSA.php new file mode 100644 index 000000000..b9a4e23eb --- /dev/null +++ b/plugins/OStatus/extlib/Crypt/RSA.php @@ -0,0 +1,1929 @@ + + * createKey()); + * + * $plaintext = 'terrafrost'; + * + * $rsa->loadKey($privatekey); + * $ciphertext = $rsa->encrypt($plaintext); + * + * $rsa->loadKey($publickey); + * echo $rsa->decrypt($ciphertext); + * ?> + * + * + * Here's an example of how to create signatures and verify signatures with this library: + * + * createKey()); + * + * $plaintext = 'terrafrost'; + * + * $rsa->loadKey($privatekey); + * $signature = $rsa->sign($plaintext); + * + * $rsa->loadKey($publickey); + * echo $rsa->verify($plaintext, $signature) ? 'verified' : 'unverified'; + * ?> + * + * + * LICENSE: This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * @category Crypt + * @package Crypt_RSA + * @author Jim Wigginton + * @copyright MMIX Jim Wigginton + * @license http://www.gnu.org/licenses/lgpl.txt + * @version $Id: RSA.php,v 1.3 2009/12/04 21:05:32 terrafrost Exp $ + * @link http://phpseclib.sourceforge.net + */ + +/** + * Include Math_BigInteger + */ +require_once('Math/BigInteger.php'); + +/** + * Include Crypt_Random + */ +require_once('Crypt/Random.php'); + +/** + * Include Crypt_Hash + */ +require_once('Crypt/Hash.php'); + +/**#@+ + * @access public + * @see Crypt_RSA::encrypt() + * @see Crypt_RSA::decrypt() + */ +/** + * Use {@link http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding Optimal Asymmetric Encryption Padding} + * (OAEP) for encryption / decryption. + * + * Uses sha1 by default. + * + * @see Crypt_RSA::setHash() + * @see Crypt_RSA::setMGFHash() + */ +define('CRYPT_RSA_ENCRYPTION_OAEP', 1); +/** + * Use PKCS#1 padding. + * + * Although CRYPT_RSA_ENCRYPTION_OAEP offers more security, including PKCS#1 padding is necessary for purposes of backwards + * compatability with protocols (like SSH-1) written before OAEP's introduction. + */ +define('CRYPT_RSA_ENCRYPTION_PKCS1', 2); +/**#@-*/ + +/**#@+ + * @access public + * @see Crypt_RSA::sign() + * @see Crypt_RSA::verify() + * @see Crypt_RSA::setHash() + */ +/** + * Use the Probabilistic Signature Scheme for signing + * + * Uses sha1 by default. + * + * @see Crypt_RSA::setSaltLength() + * @see Crypt_RSA::setMGFHash() + */ +define('CRYPT_RSA_SIGNATURE_PSS', 1); +/** + * Use the PKCS#1 scheme by default. + * + * Although CRYPT_RSA_SIGNATURE_PSS offers more security, including PKCS#1 signing is necessary for purposes of backwards + * compatability with protocols (like SSH-2) written before PSS's introduction. + */ +define('CRYPT_RSA_SIGNATURE_PKCS1', 2); +/**#@-*/ + +/**#@+ + * @access private + * @see Crypt_RSA::createKey() + */ +/** + * ASN1 Integer + */ +define('CRYPT_RSA_ASN1_INTEGER', 2); +/** + * ASN1 Sequence (with the constucted bit set) + */ +define('CRYPT_RSA_ASN1_SEQUENCE', 48); +/**#@-*/ + +/**#@+ + * @access private + * @see Crypt_RSA::Crypt_RSA() + */ +/** + * To use the pure-PHP implementation + */ +define('CRYPT_RSA_MODE_INTERNAL', 1); +/** + * To use the OpenSSL library + * + * (if enabled; otherwise, the internal implementation will be used) + */ +define('CRYPT_RSA_MODE_OPENSSL', 2); +/**#@-*/ + +/**#@+ + * @access public + * @see Crypt_RSA::createKey() + * @see Crypt_RSA::setPrivateKeyFormat() + */ +/** + * PKCS#1 formatted private key + * + * Used by OpenSSH + */ +define('CRYPT_RSA_PRIVATE_FORMAT_PKCS1', 0); +/**#@-*/ + +/**#@+ + * @access public + * @see Crypt_RSA::createKey() + * @see Crypt_RSA::setPublicKeyFormat() + */ +/** + * Raw public key + * + * An array containing two Math_BigInteger objects. + * + * The exponent can be indexed with any of the following: + * + * 0, e, exponent, publicExponent + * + * The modulus can be indexed with any of the following: + * + * 1, n, modulo, modulus + */ +define('CRYPT_RSA_PUBLIC_FORMAT_RAW', 1); +/** + * PKCS#1 formatted public key + */ +define('CRYPT_RSA_PUBLIC_FORMAT_PKCS1', 2); +/** + * OpenSSH formatted public key + * + * Place in $HOME/.ssh/authorized_keys + */ +define('CRYPT_RSA_PUBLIC_FORMAT_OPENSSH', 3); +/**#@-*/ + +/** + * Pure-PHP PKCS#1 compliant implementation of RSA. + * + * @author Jim Wigginton + * @version 0.1.0 + * @access public + * @package Crypt_RSA + */ +class Crypt_RSA { + /** + * Precomputed Zero + * + * @var Array + * @access private + */ + var $zero; + + /** + * Precomputed One + * + * @var Array + * @access private + */ + var $one; + + /** + * Private Key Format + * + * @var Integer + * @access private + */ + var $privateKeyFormat = CRYPT_RSA_PRIVATE_FORMAT_PKCS1; + + /** + * Public Key Format + * + * @var Integer + * @access public + */ + var $publicKeyFormat = CRYPT_RSA_PUBLIC_FORMAT_PKCS1; + + /** + * Modulus (ie. n) + * + * @var Math_BigInteger + * @access private + */ + var $modulus; + + /** + * Modulus length + * + * @var Math_BigInteger + * @access private + */ + var $k; + + /** + * Exponent (ie. e or d) + * + * @var Math_BigInteger + * @access private + */ + var $exponent; + + /** + * Primes for Chinese Remainder Theorem (ie. p and q) + * + * @var Array + * @access private + */ + var $primes; + + /** + * Exponents for Chinese Remainder Theorem (ie. dP and dQ) + * + * @var Array + * @access private + */ + var $exponents; + + /** + * Coefficients for Chinese Remainder Theorem (ie. qInv) + * + * @var Array + * @access private + */ + var $coefficients; + + /** + * Hash name + * + * @var String + * @access private + */ + var $hashName; + + /** + * Hash function + * + * @var Crypt_Hash + * @access private + */ + var $hash; + + /** + * Length of hash function output + * + * @var Integer + * @access private + */ + var $hLen; + + /** + * Length of salt + * + * @var Integer + * @access private + */ + var $sLen; + + /** + * Hash function for the Mask Generation Function + * + * @var Crypt_Hash + * @access private + */ + var $mgfHash; + + /** + * Encryption mode + * + * @var Integer + * @access private + */ + var $encryptionMode = CRYPT_RSA_ENCRYPTION_OAEP; + + /** + * Signature mode + * + * @var Integer + * @access private + */ + var $signatureMode = CRYPT_RSA_SIGNATURE_PSS; + + /** + * Public Exponent + * + * @var Mixed + * @access private + */ + var $publicExponent = false; + + /** + * Password + * + * @var String + * @access private + */ + var $password = ''; + + /** + * The constructor + * + * If you want to make use of the openssl extension, you'll need to set the mode manually, yourself. The reason + * Crypt_RSA doesn't do it is because OpenSSL doesn't fail gracefully. openssl_pkey_new(), in particular, requires + * openssl.cnf be present somewhere and, unfortunately, the only real way to find out is too late. + * + * @return Crypt_RSA + * @access public + */ + function Crypt_RSA() + { + if ( !defined('CRYPT_RSA_MODE') ) { + switch (true) { + //case extension_loaded('openssl') && version_compare(PHP_VERSION, '4.2.0', '>='): + // define('CRYPT_RSA_MODE', CRYPT_RSA_MODE_OPENSSL); + // break; + default: + define('CRYPT_RSA_MODE', CRYPT_RSA_MODE_INTERNAL); + } + } + + $this->zero = new Math_BigInteger(); + $this->one = new Math_BigInteger(1); + + $this->hash = new Crypt_Hash('sha1'); + $this->hLen = $this->hash->getLength(); + $this->hashName = 'sha1'; + $this->mgfHash = new Crypt_Hash('sha1'); + } + + /** + * Create public / private key pair + * + * Returns an array with the following three elements: + * - 'privatekey': The private key. + * - 'publickey': The public key. + * - 'partialkey': A partially computed key (if the execution time exceeded $timeout). + * Will need to be passed back to Crypt_RSA::createKey() as the third parameter for further processing. + * + * @access public + * @param optional Integer $bits + * @param optional Integer $timeout + * @param optional Math_BigInteger $p + */ + function createKey($bits = 1024, $timeout = false, $primes = array()) + { + if ( CRYPT_RSA_MODE == CRYPT_RSA_MODE_OPENSSL ) { + $rsa = openssl_pkey_new(array('private_key_bits' => $bits)); + openssl_pkey_export($rsa, $privatekey); + $publickey = openssl_pkey_get_details($rsa); + $publickey = $publickey['key']; + + if ($this->privateKeyFormat != CRYPT_RSA_PRIVATE_FORMAT_PKCS1) { + $privatekey = call_user_func_array(array($this, '_convertPrivateKey'), array_values($this->_parseKey($privatekey, CRYPT_RSA_PRIVATE_FORMAT_PKCS1))); + $publickey = call_user_func_array(array($this, '_convertPublicKey'), array_values($this->_parseKey($publickey, CRYPT_RSA_PUBLIC_FORMAT_PKCS1))); + } + + return array( + 'privatekey' => $privatekey, + 'publickey' => $publickey, + 'partialkey' => false + ); + } + + static $e; + if (!isset($e)) { + if (!defined('CRYPT_RSA_EXPONENT')) { + // http://en.wikipedia.org/wiki/65537_%28number%29 + define('CRYPT_RSA_EXPONENT', '65537'); + } + if (!defined('CRYPT_RSA_COMMENT')) { + define('CRYPT_RSA_COMMENT', 'phpseclib-generated-key'); + } + // per , this number ought not result in primes smaller + // than 256 bits. + if (!defined('CRYPT_RSA_SMALLEST_PRIME')) { + define('CRYPT_RSA_SMALLEST_PRIME', 4096); + } + + $e = new Math_BigInteger(CRYPT_RSA_EXPONENT); + } + + extract($this->_generateMinMax($bits)); + $absoluteMin = $min; + $temp = $bits >> 1; + if ($temp > CRYPT_RSA_SMALLEST_PRIME) { + $num_primes = floor($bits / CRYPT_RSA_SMALLEST_PRIME); + $temp = CRYPT_RSA_SMALLEST_PRIME; + } else { + $num_primes = 2; + } + extract($this->_generateMinMax($temp + $bits % $temp)); + $finalMax = $max; + extract($this->_generateMinMax($temp)); + + $exponents = $coefficients = array(); + $generator = new Math_BigInteger(); + $generator->setRandomGenerator('crypt_random'); + + $n = $this->one->copy(); + $lcm = array( + 'top' => $this->one->copy(), + 'bottom' => false + ); + + $start = time(); + $i0 = count($primes) + 1; + + do { + for ($i = $i0; $i <= $num_primes; $i++) { + if ($timeout !== false) { + $timeout-= time() - $start; + $start = time(); + if ($timeout <= 0) { + return array( + 'privatekey' => '', + 'publickey' => '', + 'partialkey' => $primes + ); + } + } + if ($i == $num_primes) { + list($min, $temp) = $absoluteMin->divide($n); + if (!$temp->equals($this->zero)) { + $min = $min->add($this->one); // ie. ceil() + } + $primes[$i] = $generator->randomPrime($min, $finalMax, $timeout); + } else { + $primes[$i] = $generator->randomPrime($min, $max, $timeout); + } + + if ($primes[$i] === false) { // if we've reached the timeout + return array( + 'privatekey' => '', + 'publickey' => '', + 'partialkey' => array_slice($primes, 0, $i - 1) + ); + } + + // the first coefficient is calculated differently from the rest + // ie. instead of being $primes[1]->modInverse($primes[2]), it's $primes[2]->modInverse($primes[1]) + if ($i > 2) { + $coefficients[$i] = $n->modInverse($primes[$i]); + } + + $n = $n->multiply($primes[$i]); + + $temp = $primes[$i]->subtract($this->one); + + // textbook RSA implementations use Euler's totient function instead of the least common multiple. + // see http://en.wikipedia.org/wiki/Euler%27s_totient_function + $lcm['top'] = $lcm['top']->multiply($temp); + $lcm['bottom'] = $lcm['bottom'] === false ? $temp : $lcm['bottom']->gcd($temp); + + $exponents[$i] = $e->modInverse($temp); + } + + list($lcm) = $lcm['top']->divide($lcm['bottom']); + $gcd = $lcm->gcd($e); + $i0 = 1; + } while (!$gcd->equals($this->one)); + + $d = $e->modInverse($lcm); + + $coefficients[2] = $primes[2]->modInverse($primes[1]); + + // from : + // RSAPrivateKey ::= SEQUENCE { + // version Version, + // modulus INTEGER, -- n + // publicExponent INTEGER, -- e + // privateExponent INTEGER, -- d + // prime1 INTEGER, -- p + // prime2 INTEGER, -- q + // exponent1 INTEGER, -- d mod (p-1) + // exponent2 INTEGER, -- d mod (q-1) + // coefficient INTEGER, -- (inverse of q) mod p + // otherPrimeInfos OtherPrimeInfos OPTIONAL + // } + + return array( + 'privatekey' => $this->_convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients), + 'publickey' => $this->_convertPublicKey($n, $e), + 'partialkey' => false + ); + } + + /** + * Convert a private key to the appropriate format. + * + * @access private + * @see setPrivateKeyFormat() + * @param String $RSAPrivateKey + * @return String + */ + function _convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients) + { + $num_primes = count($primes); + + $raw = array( + 'version' => $num_primes == 2 ? chr(0) : chr(1), // two-prime vs. multi + 'modulus' => $n->toBytes(true), + 'publicExponent' => $e->toBytes(true), + 'privateExponent' => $d->toBytes(true), + 'prime1' => $primes[1]->toBytes(true), + 'prime2' => $primes[2]->toBytes(true), + 'exponent1' => $exponents[1]->toBytes(true), + 'exponent2' => $exponents[2]->toBytes(true), + 'coefficient' => $coefficients[2]->toBytes(true) + ); + + // if the format in question does not support multi-prime rsa and multi-prime rsa was used, + // call _convertPublicKey() instead. + switch ($this->privateKeyFormat) { + default: // eg. CRYPT_RSA_PRIVATE_FORMAT_PKCS1 + $components = array(); + foreach ($raw as $name => $value) { + $components[$name] = pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($value)), $value); + } + + $RSAPrivateKey = implode('', $components); + + if ($num_primes > 2) { + $OtherPrimeInfos = ''; + for ($i = 3; $i <= $num_primes; $i++) { + // OtherPrimeInfos ::= SEQUENCE SIZE(1..MAX) OF OtherPrimeInfo + // + // OtherPrimeInfo ::= SEQUENCE { + // prime INTEGER, -- ri + // exponent INTEGER, -- di + // coefficient INTEGER -- ti + // } + $OtherPrimeInfo = pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($primes[$i]->toBytes(true))), $primes[$i]->toBytes(true)); + $OtherPrimeInfo.= pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($exponents[$i]->toBytes(true))), $exponents[$i]->toBytes(true)); + $OtherPrimeInfo.= pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($coefficients[$i]->toBytes(true))), $coefficients[$i]->toBytes(true)); + $OtherPrimeInfos.= pack('Ca*a*', CRYPT_RSA_ASN1_SEQUENCE, $this->_encodeLength(strlen($OtherPrimeInfo)), $OtherPrimeInfo); + } + $RSAPrivateKey.= pack('Ca*a*', CRYPT_RSA_ASN1_SEQUENCE, $this->_encodeLength(strlen($OtherPrimeInfos)), $OtherPrimeInfos); + } + + $RSAPrivateKey = pack('Ca*a*', CRYPT_RSA_ASN1_SEQUENCE, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey); + + if (!empty($this->password)) { + $iv = $this->_random(8); + $symkey = pack('H*', md5($this->password . $iv)); // symkey is short for symmetric key + $symkey.= substr(pack('H*', md5($symkey . $this->password . $iv)), 0, 8); + if (!class_exists('Crypt_TripleDES')) { + require_once('Crypt/TripleDES.php'); + } + $des = new Crypt_TripleDES(); + $des->setKey($symkey); + $des->setIV($iv); + $iv = strtoupper(bin2hex($iv)); + $RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n" . + "Proc-Type: 4,ENCRYPTED\r\n" . + "DEK-Info: DES-EDE3-CBC,$iv\r\n" . + "\r\n" . + chunk_split(base64_encode($des->encrypt($RSAPrivateKey))) . + '-----END RSA PRIVATE KEY-----'; + } else { + $RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n" . + chunk_split(base64_encode($RSAPrivateKey)) . + '-----END RSA PRIVATE KEY-----'; + } + + return $RSAPrivateKey; + } + } + + /** + * Convert a public key to the appropriate format + * + * @access private + * @see setPublicKeyFormat() + * @param String $RSAPrivateKey + * @return String + */ + function _convertPublicKey($n, $e) + { + $modulus = $n->toBytes(true); + $publicExponent = $e->toBytes(true); + + switch ($this->publicKeyFormat) { + case CRYPT_RSA_PUBLIC_FORMAT_RAW: + return array('e' => $e->copy(), 'n' => $n->copy()); + case CRYPT_RSA_PUBLIC_FORMAT_OPENSSH: + // from : + // string "ssh-rsa" + // mpint e + // mpint n + $RSAPublicKey = pack('Na*Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($publicExponent), $publicExponent, strlen($modulus), $modulus); + $RSAPublicKey = 'ssh-rsa ' . base64_encode($RSAPublicKey) . ' ' . CRYPT_RSA_COMMENT; + + return $RSAPublicKey; + default: // eg. CRYPT_RSA_PUBLIC_FORMAT_PKCS1 + // from : + // RSAPublicKey ::= SEQUENCE { + // modulus INTEGER, -- n + // publicExponent INTEGER -- e + // } + $components = array( + 'modulus' => pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($modulus)), $modulus), + 'publicExponent' => pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($publicExponent)), $publicExponent) + ); + + $RSAPublicKey = pack('Ca*a*a*', + CRYPT_RSA_ASN1_SEQUENCE, $this->_encodeLength(strlen($components['modulus']) + strlen($components['publicExponent'])), + $components['modulus'], $components['publicExponent'] + ); + + $RSAPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" . + chunk_split(base64_encode($RSAPublicKey)) . + '-----END PUBLIC KEY-----'; + + return $RSAPublicKey; + } + } + + /** + * Break a public or private key down into its constituant components + * + * @access private + * @see _convertPublicKey() + * @see _convertPrivateKey() + * @param String $key + * @param Integer $type + * @return Array + */ + function _parseKey($key, $type) + { + switch ($type) { + case CRYPT_RSA_PUBLIC_FORMAT_RAW: + if (!is_array($key)) { + return false; + } + $components = array(); + switch (true) { + case isset($key['e']): + $components['publicExponent'] = $key['e']->copy(); + break; + case isset($key['exponent']): + $components['publicExponent'] = $key['exponent']->copy(); + break; + case isset($key['publicExponent']): + $components['publicExponent'] = $key['publicExponent']->copy(); + break; + case isset($key[0]): + $components['publicExponent'] = $key[0]->copy(); + } + switch (true) { + case isset($key['n']): + $components['modulus'] = $key['n']->copy(); + break; + case isset($key['modulo']): + $components['modulus'] = $key['modulo']->copy(); + break; + case isset($key['modulus']): + $components['modulus'] = $key['modulus']->copy(); + break; + case isset($key[1]): + $components['modulus'] = $key[1]->copy(); + } + return $components; + case CRYPT_RSA_PRIVATE_FORMAT_PKCS1: + case CRYPT_RSA_PUBLIC_FORMAT_PKCS1: + /* Although PKCS#1 proposes a format that public and private keys can use, encrypting them is + "outside the scope" of PKCS#1. PKCS#1 then refers you to PKCS#12 and PKCS#15 if you're wanting to + protect private keys, however, that's not what OpenSSL* does. OpenSSL protects private keys by adding + two new "fields" to the key - DEK-Info and Proc-Type. These fields are discussed here: + + http://tools.ietf.org/html/rfc1421#section-4.6.1.1 + http://tools.ietf.org/html/rfc1421#section-4.6.1.3 + + DES-EDE3-CBC as an algorithm, however, is not discussed anywhere, near as I can tell. + DES-CBC and DES-EDE are discussed in RFC1423, however, DES-EDE3-CBC isn't, nor is its key derivation + function. As is, the definitive authority on this encoding scheme isn't the IETF but rather OpenSSL's + own implementation. ie. the implementation *is* the standard and any bugs that may exist in that + implementation are part of the standard, as well. + + * OpenSSL is the de facto standard. It's utilized by OpenSSH and other projects */ + if (preg_match('#DEK-Info: DES-EDE3-CBC,(.+)#', $key, $matches)) { + $iv = pack('H*', trim($matches[1])); + $symkey = pack('H*', md5($this->password . $iv)); // symkey is short for symmetric key + $symkey.= substr(pack('H*', md5($symkey . $this->password . $iv)), 0, 8); + $ciphertext = base64_decode(preg_replace('#.+(\r|\n|\r\n)\1|[\r\n]|-.+-#s', '', $key)); + if ($ciphertext === false) { + return false; + } + if (!class_exists('Crypt_TripleDES')) { + require_once('Crypt/TripleDES.php'); + } + $des = new Crypt_TripleDES(); + $des->setKey($symkey); + $des->setIV($iv); + $key = $des->decrypt($ciphertext); + } else { + $key = base64_decode(preg_replace('#-.+-|[\r\n]#', '', $key)); + if ($key === false) { + return false; + } + } + + $private = false; + $components = array(); + + $this->_string_shift($key); // skip over CRYPT_RSA_ASN1_SEQUENCE + $this->_decodeLength($key); // skip over the length of the above sequence + $this->_string_shift($key); // skip over CRYPT_RSA_ASN1_INTEGER + $length = $this->_decodeLength($key); + $temp = $this->_string_shift($key, $length); + if (strlen($temp) != 1 || ord($temp) > 2) { + $components['modulus'] = new Math_BigInteger($temp, -256); + $this->_string_shift($key); // skip over CRYPT_RSA_ASN1_INTEGER + $length = $this->_decodeLength($key); + $components[$type == CRYPT_RSA_PUBLIC_FORMAT_PKCS1 ? 'publicExponent' : 'privateExponent'] = new Math_BigInteger($this->_string_shift($key, $length), -256); + + return $components; + } + $this->_string_shift($key); // skip over CRYPT_RSA_ASN1_INTEGER + $length = $this->_decodeLength($key); + $components['modulus'] = new Math_BigInteger($this->_string_shift($key, $length), -256); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['publicExponent'] = new Math_BigInteger($this->_string_shift($key, $length), -256); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['privateExponent'] = new Math_BigInteger($this->_string_shift($key, $length), -256); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['primes'] = array(1 => new Math_BigInteger($this->_string_shift($key, $length), -256)); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['primes'][] = new Math_BigInteger($this->_string_shift($key, $length), -256); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['exponents'] = array(1 => new Math_BigInteger($this->_string_shift($key, $length), -256)); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['exponents'][] = new Math_BigInteger($this->_string_shift($key, $length), -256); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['coefficients'] = array(2 => new Math_BigInteger($this->_string_shift($key, $length), -256)); + if (!empty($key)) { + $key = substr($key, 1); // skip over CRYPT_RSA_ASN1_SEQUENCE + $this->_decodeLength($key); + while (!empty($key)) { + $key = substr($key, 1); // skip over CRYPT_RSA_ASN1_SEQUENCE + $this->_decodeLength($key); + $key = substr($key, 1); + $length = $this->_decodeLength($key); + $components['primes'][] = new Math_BigInteger($this->_string_shift($key, $length), -256); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['exponents'][] = new Math_BigInteger($this->_string_shift($key, $length), -256); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['coefficients'][] = new Math_BigInteger($this->_string_shift($key, $length), -256); + } + } + + return $components; + case CRYPT_RSA_PUBLIC_FORMAT_OPENSSH: + $key = base64_decode(preg_replace('#^ssh-rsa | .+$#', '', $key)); + if ($key === false) { + return false; + } + + $components = array(); + extract(unpack('Nlength', $this->_string_shift($key, 4))); + $components['modulus'] = new Math_BigInteger($this->_string_shift($key, $length), -256); + extract(unpack('Nlength', $this->_string_shift($key, 4))); + $components['publicExponent'] = new Math_BigInteger($this->_string_shift($key, $length), -256); + + return $components; + } + } + + /** + * Loads a public or private key + * + * @access public + * @param String $key + * @param Integer $type optional + */ + function loadKey($key, $type = CRYPT_RSA_PRIVATE_FORMAT_PKCS1) + { + $components = $this->_parseKey($key, $type); + $this->modulus = $components['modulus']; + $this->k = strlen($this->modulus->toBytes()); + $this->exponent = isset($components['privateExponent']) ? $components['privateExponent'] : $components['publicExponent']; + if (isset($components['primes'])) { + $this->primes = $components['primes']; + $this->exponents = $components['exponents']; + $this->coefficients = $components['coefficients']; + $this->publicExponent = $components['publicExponent']; + } else { + $this->primes = array(); + $this->exponents = array(); + $this->coefficients = array(); + $this->publicExponent = false; + } + } + + /** + * Sets the password + * + * Private keys can be encrypted with a password. To unset the password, pass in the empty string or false. + * Or rather, pass in $password such that empty($password) is true. + * + * @see createKey() + * @see loadKey() + * @access public + * @param String $password + */ + function setPassword($password) + { + $this->password = $password; + } + + /** + * Defines the public key + * + * Some private key formats define the public exponent and some don't. Those that don't define it are problematic when + * used in certain contexts. For example, in SSH-2, RSA authentication works by sending the public key along with a + * message signed by the private key to the server. The SSH-2 server looks the public key up in an index of public keys + * and if it's present then proceeds to verify the signature. Problem is, if your private key doesn't include the public + * exponent this won't work unless you manually add the public exponent. + * + * Do note that when a new key is loaded the index will be cleared. + * + * Returns true on success, false on failure + * + * @see getPublicKey() + * @access public + * @param String $key + * @param Integer $type optional + * @return Boolean + */ + function setPublicKey($key, $type = CRYPT_RSA_PUBLIC_FORMAT_PKCS1) + { + $components = $this->_parseKey($key, $type); + if (!$this->modulus->equals($components['modulus'])) { + return false; + } + $this->publicExponent = $components['publicExponent']; + } + + /** + * Returns the public key + * + * The public key is only returned under two circumstances - if the private key had the public key embedded within it + * or if the public key was set via setPublicKey(). If the currently loaded key is supposed to be the public key this + * function won't return it since this library, for the most part, doesn't distinguish between public and private keys. + * + * @see getPublicKey() + * @access public + * @param String $key + * @param Integer $type optional + */ + function getPublicKey($type = CRYPT_RSA_PUBLIC_FORMAT_PKCS1) + { + $oldFormat = $this->publicKeyFormat; + $this->publicKeyFormat = $type; + $temp = $this->_convertPublicKey($this->modulus, $this->publicExponent); + $this->publicKeyFormat = $oldFormat; + return $temp; + } + + /** + * Generates the smallest and largest numbers requiring $bits bits + * + * @access private + * @param Integer $bits + * @return Array + */ + function _generateMinMax($bits) + { + $bytes = $bits >> 3; + $min = str_repeat(chr(0), $bytes); + $max = str_repeat(chr(0xFF), $bytes); + $msb = $num_bits & 7; + if ($msb) { + $min = chr(1 << ($msb - 1)) . $min; + $max = chr((1 << $msb) - 1) . $max; + } else { + $min[0] = chr(0x80); + } + + return array( + 'min' => new Math_BigInteger($min, 256), + 'max' => new Math_BigInteger($max, 256) + ); + } + + /** + * DER-decode the length + * + * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See + * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 § 8.1.3} for more information. + * + * @access private + * @param String $string + * @return Integer + */ + function _decodeLength(&$string) + { + $length = ord($this->_string_shift($string)); + if ( $length & 0x80 ) { // definite length, long form + $length&= 0x7F; + $temp = $this->_string_shift($string, $length); + $start+= $length; + list(, $length) = unpack('N', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4)); + } + return $length; + } + + /** + * DER-encode the length + * + * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See + * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 § 8.1.3} for more information. + * + * @access private + * @param Integer $length + * @return String + */ + function _encodeLength($length) + { + if ($length <= 0x7F) { + return chr($length); + } + + $temp = ltrim(pack('N', $length), chr(0)); + return pack('Ca*', 0x80 | strlen($temp), $temp); + } + + /** + * String Shift + * + * Inspired by array_shift + * + * @param String $string + * @param optional Integer $index + * @return String + * @access private + */ + function _string_shift(&$string, $index = 1) + { + $substr = substr($string, 0, $index); + $string = substr($string, $index); + return $substr; + } + + /** + * Determines the private key format + * + * @see createKey() + * @access public + * @param Integer $format + */ + function setPrivateKeyFormat($format) + { + $this->privateKeyFormat = $format; + } + + /** + * Determines the public key format + * + * @see createKey() + * @access public + * @param Integer $format + */ + function setPublicKeyFormat($format) + { + $this->publicKeyFormat = $format; + } + + /** + * Determines which hashing function should be used + * + * Used with signature production / verification and (if the encryption mode is CRYPT_RSA_ENCRYPTION_OAEP) encryption and + * decryption. If $hash isn't supported, sha1 is used. + * + * @access public + * @param String $hash + */ + function setHash($hash) + { + // Crypt_Hash supports algorithms that PKCS#1 doesn't support. md5-96 and sha1-96, for example. + switch ($hash) { + case 'md2': + case 'md5': + case 'sha1': + case 'sha256': + case 'sha384': + case 'sha512': + $this->hash = new Crypt_Hash($hash); + $this->hLen = $this->hash->getLength(); + $this->hashName = $hash; + break; + default: + $this->hash = new Crypt_Hash('sha1'); + $this->hLen = $this->hash->getLength(); + $this->hashName = 'sha1'; + } + } + + /** + * Determines which hashing function should be used for the mask generation function + * + * The mask generation function is used by CRYPT_RSA_ENCRYPTION_OAEP and CRYPT_RSA_SIGNATURE_PSS and although it's + * best if Hash and MGFHash are set to the same thing this is not a requirement. + * + * @access public + * @param String $hash + */ + function setMGFHash($hash) + { + // Crypt_Hash supports algorithms that PKCS#1 doesn't support. md5-96 and sha1-96, for example. + switch ($hash) { + case 'md2': + case 'md5': + case 'sha1': + case 'sha256': + case 'sha384': + case 'sha512': + $this->mgfHash = new Crypt_Hash($hash); + break; + default: + $this->mgfHash = new Crypt_Hash('sha1'); + } + } + + /** + * Determines the salt length + * + * To quote from {@link http://tools.ietf.org/html/rfc3447#page-38 RFC3447#page-38}: + * + * Typical salt lengths in octets are hLen (the length of the output + * of the hash function Hash) and 0. + * + * @access public + * @param Integer $format + */ + function setSaltLength($sLen) + { + $this->sLen = $sLen; + } + + /** + * Generates a random string x bytes long + * + * @access public + * @param Integer $bytes + * @param optional Integer $nonzero + * @return String + */ + function _random($bytes, $nonzero = false) + { + $temp = ''; + if ($nonzero) { + for ($i = 0; $i < $bytes; $i++) { + $temp.= chr(crypt_random(1, 255)); + } + } else { + $ints = ($bytes + 1) >> 2; + for ($i = 0; $i < $ints; $i++) { + $temp.= pack('N', crypt_random()); + } + $temp = substr($temp, 0, $bytes); + } + return $temp; + } + + /** + * Integer-to-Octet-String primitive + * + * See {@link http://tools.ietf.org/html/rfc3447#section-4.1 RFC3447#section-4.1}. + * + * @access private + * @param Math_BigInteger $x + * @param Integer $xLen + * @return String + */ + function _i2osp($x, $xLen) + { + $x = $x->toBytes(); + if (strlen($x) > $xLen) { + user_error('Integer too large', E_USER_NOTICE); + return false; + } + return str_pad($x, $xLen, chr(0), STR_PAD_LEFT); + } + + /** + * Octet-String-to-Integer primitive + * + * See {@link http://tools.ietf.org/html/rfc3447#section-4.2 RFC3447#section-4.2}. + * + * @access private + * @param String $x + * @return Math_BigInteger + */ + function _os2ip($x) + { + return new Math_BigInteger($x, 256); + } + + /** + * Exponentiate with or without Chinese Remainder Theorem + * + * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.1 RFC3447#section-5.1.2}. + * + * @access private + * @param Math_BigInteger $x + * @return Math_BigInteger + */ + function _exponentiate($x) + { + if (empty($this->primes) || empty($this->coefficients) || empty($this->exponents)) { + return $x->modPow($this->exponent, $this->modulus); + } + + $num_primes = count($this->primes); + $m_i = array( + 1 => $x->modPow($this->exponents[1], $this->primes[1]), + 2 => $x->modPow($this->exponents[2], $this->primes[2]) + ); + $h = $m_i[1]->subtract($m_i[2]); + $h = $h->multiply($this->coefficients[2]); + list(, $h) = $h->divide($this->primes[1]); + $m = $m_i[2]->add($h->multiply($this->primes[2])); + + $r = $this->primes[1]; + for ($i = 3; $i <= $num_primes; $i++) { + $m_i = $x->modPow($this->exponents[$i], $this->primes[$i]); + + $r = $r->multiply($this->primes[$i - 1]); + + $h = $m_i->subtract($m); + $h = $h->multiply($this->coefficients[$i]); + list(, $h) = $h->divide($this->primes[$i]); + + $m = $m->add($r->multiply($h)); + } + + return $m; + } + + /** + * RSAEP + * + * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.1 RFC3447#section-5.1.1}. + * + * @access private + * @param Math_BigInteger $m + * @return Math_BigInteger + */ + function _rsaep($m) + { + if ($m->compare($this->zero) < 0 || $m->compare($this->modulus) > 0) { + user_error('Message representative out of range', E_USER_NOTICE); + return false; + } + return $this->_exponentiate($m); + } + + /** + * RSADP + * + * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.2 RFC3447#section-5.1.2}. + * + * @access private + * @param Math_BigInteger $c + * @return Math_BigInteger + */ + function _rsadp($c) + { + if ($c->compare($this->zero) < 0 || $c->compare($this->modulus) > 0) { + user_error('Ciphertext representative out of range', E_USER_NOTICE); + return false; + } + return $this->_exponentiate($c); + } + + /** + * RSASP1 + * + * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.1 RFC3447#section-5.2.1}. + * + * @access private + * @param Math_BigInteger $m + * @return Math_BigInteger + */ + function _rsasp1($m) + { + if ($m->compare($this->zero) < 0 || $m->compare($this->modulus) > 0) { + user_error('Message representative out of range', E_USER_NOTICE); + return false; + } + return $this->_exponentiate($m); + } + + /** + * RSAVP1 + * + * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.2 RFC3447#section-5.2.2}. + * + * @access private + * @param Math_BigInteger $s + * @return Math_BigInteger + */ + function _rsavp1($s) + { + if ($s->compare($this->zero) < 0 || $s->compare($this->modulus) > 0) { + user_error('Signature representative out of range', E_USER_NOTICE); + return false; + } + return $this->_exponentiate($s); + } + + /** + * MGF1 + * + * See {@link http://tools.ietf.org/html/rfc3447#section-B.2.1 RFC3447#section-B.2.1}. + * + * @access private + * @param String $mgfSeed + * @param Integer $mgfLen + * @return String + */ + function _mgf1($mgfSeed, $maskLen) + { + // if $maskLen would yield strings larger than 4GB, PKCS#1 suggests a "Mask too long" error be output. + + $t = ''; + $count = ceil($maskLen / $this->hLen); + for ($i = 0; $i < $count; $i++) { + $c = pack('N', $i); + $t.= $this->mgfHash->hash($mgfSeed . $c); + } + + return substr($t, 0, $maskLen); + } + + /** + * RSAES-OAEP-ENCRYPT + * + * See {@link http://tools.ietf.org/html/rfc3447#section-7.1.1 RFC3447#section-7.1.1} and + * {http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding OAES}. + * + * @access private + * @param String $m + * @param String $l + * @return String + */ + function _rsaes_oaep_encrypt($m, $l = '') + { + $mLen = strlen($m); + + // Length checking + + // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error + // be output. + + if ($mLen > $this->k - 2 * $this->hLen - 2) { + user_error('Message too long', E_USER_NOTICE); + return false; + } + + // EME-OAEP encoding + + $lHash = $this->hash->hash($l); + $ps = str_repeat(chr(0), $this->k - $mLen - 2 * $this->hLen - 2); + $db = $lHash . $ps . chr(1) . $m; + $seed = $this->_random($this->hLen); + $dbMask = $this->_mgf1($seed, $this->k - $this->hLen - 1); + $maskedDB = $db ^ $dbMask; + $seedMask = $this->_mgf1($maskedDB, $this->hLen); + $maskedSeed = $seed ^ $seedMask; + $em = chr(0) . $maskedSeed . $maskedDB; + + // RSA encryption + + $m = $this->_os2ip($em); + $c = $this->_rsaep($m); + $c = $this->_i2osp($c, $this->k); + + // Output the ciphertext C + + return $c; + } + + /** + * RSAES-OAEP-DECRYPT + * + * See {@link http://tools.ietf.org/html/rfc3447#section-7.1.2 RFC3447#section-7.1.2}. The fact that the error + * messages aren't distinguishable from one another hinders debugging, but, to quote from RFC3447#section-7.1.2: + * + * Note. Care must be taken to ensure that an opponent cannot + * distinguish the different error conditions in Step 3.g, whether by + * error message or timing, or, more generally, learn partial + * information about the encoded message EM. Otherwise an opponent may + * be able to obtain useful information about the decryption of the + * ciphertext C, leading to a chosen-ciphertext attack such as the one + * observed by Manger [36]. + * + * As for $l... to quote from {@link http://tools.ietf.org/html/rfc3447#page-17 RFC3447#page-17}: + * + * Both the encryption and the decryption operations of RSAES-OAEP take + * the value of a label L as input. In this version of PKCS #1, L is + * the empty string; other uses of the label are outside the scope of + * this document. + * + * @access private + * @param String $c + * @param String $l + * @return String + */ + function _rsaes_oaep_decrypt($c, $l = '') + { + // Length checking + + // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error + // be output. + + if (strlen($c) != $this->k || $this->k < 2 * $this->hLen + 2) { + user_error('Decryption error', E_USER_NOTICE); + return false; + } + + // RSA decryption + + $c = $this->_os2ip($c); + $m = $this->_rsadp($c); + if ($m === false) { + user_error('Decryption error', E_USER_NOTICE); + return false; + } + $em = $this->_i2osp($m, $this->k); + + // EME-OAEP decoding + + $lHash = $this->hash->hash($l); + $y = ord($em[0]); + $maskedSeed = substr($em, 1, $this->hLen); + $maskedDB = substr($em, $this->hLen + 1); + $seedMask = $this->_mgf1($maskedDB, $this->hLen); + $seed = $maskedSeed ^ $seedMask; + $dbMask = $this->_mgf1($seed, $this->k - $this->hLen - 1); + $db = $maskedDB ^ $dbMask; + $lHash2 = substr($db, 0, $this->hLen); + $m = substr($db, $this->hLen); + if ($lHash != $lHash2) { + user_error('Decryption error', E_USER_NOTICE); + return false; + } + $m = ltrim($m, chr(0)); + if (ord($m[0]) != 1) { + user_error('Decryption error', E_USER_NOTICE); + return false; + } + + // Output the message M + + return substr($m, 1); + } + + /** + * RSAES-PKCS1-V1_5-ENCRYPT + * + * See {@link http://tools.ietf.org/html/rfc3447#section-7.2.1 RFC3447#section-7.2.1}. + * + * @access private + * @param String $m + * @return String + */ + function _rsaes_pkcs1_v1_5_encrypt($m) + { + $mLen = strlen($m); + + // Length checking + + if ($mLen > $this->k - 11) { + user_error('Message too long', E_USER_NOTICE); + return false; + } + + // EME-PKCS1-v1_5 encoding + + $ps = $this->_random($this->k - $mLen - 3, true); + $em = chr(0) . chr(2) . $ps . chr(0) . $m; + + // RSA encryption + $m = $this->_os2ip($em); + $c = $this->_rsaep($m); + $c = $this->_i2osp($c, $this->k); + + // Output the ciphertext C + + return $c; + } + + /** + * RSAES-PKCS1-V1_5-DECRYPT + * + * See {@link http://tools.ietf.org/html/rfc3447#section-7.2.2 RFC3447#section-7.2.2}. + * + * @access private + * @param String $c + * @return String + */ + function _rsaes_pkcs1_v1_5_decrypt($c) + { + // Length checking + + if (strlen($c) != $this->k) { // or if k < 11 + user_error('Decryption error', E_USER_NOTICE); + return false; + } + + // RSA decryption + + $c = $this->_os2ip($c); + $m = $this->_rsadp($c); + if ($m === false) { + user_error('Decryption error', E_USER_NOTICE); + return false; + } + $em = $this->_i2osp($m, $this->k); + + // EME-PKCS1-v1_5 decoding + + if (ord($em[0]) != 0 || ord($em[1]) != 2) { + user_error('Decryption error', E_USER_NOTICE); + return false; + } + + $ps = substr($em, 2, strpos($em, chr(0), 2) - 2); + $m = substr($em, strlen($ps) + 3); + + if (strlen($ps) < 8) { + user_error('Decryption error', E_USER_NOTICE); + return false; + } + + // Output M + + return $m; + } + + /** + * EMSA-PSS-ENCODE + * + * See {@link http://tools.ietf.org/html/rfc3447#section-9.1.1 RFC3447#section-9.1.1}. + * + * @access private + * @param String $m + * @param Integer $emBits + */ + function _emsa_pss_encode($m, $emBits) + { + // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error + // be output. + + $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8) + $sLen = $this->sLen == false ? $this->hLen : $this->sLen; + + $mHash = $this->hash->hash($m); + if ($emLen < $this->hLen + $sLen + 2) { + user_error('Encoding error', E_USER_NOTICE); + return false; + } + + $salt = $this->_random($sLen); + $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt; + $h = $this->hash->hash($m2); + $ps = str_repeat(chr(0), $emLen - $sLen - $this->hLen - 2); + $db = $ps . chr(1) . $salt; + $dbMask = $this->_mgf1($h, $emLen - $this->hLen - 1); + $maskedDB = $db ^ $dbMask; + $maskedDB[0] = ~chr(0xFF << ($emBits & 7)) & $maskedDB[0]; + $em = $maskedDB . $h . chr(0xBC); + + return $em; + } + + /** + * EMSA-PSS-VERIFY + * + * See {@link http://tools.ietf.org/html/rfc3447#section-9.1.2 RFC3447#section-9.1.2}. + * + * @access private + * @param String $m + * @param String $em + * @param Integer $emBits + * @return String + */ + function _emsa_pss_verify($m, $em, $emBits) + { + // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error + // be output. + + $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8); + $sLen = $this->sLen == false ? $this->hLen : $this->sLen; + + $mHash = $this->hash->hash($m); + if ($emLen < $this->hLen + $sLen + 2) { + return false; + } + + if ($em[strlen($em) - 1] != chr(0xBC)) { + return false; + } + + $maskedDB = substr($em, 0, $em - $this->hLen - 1); + $h = substr($em, $em - $this->hLen - 1, $this->hLen); + $temp = chr(0xFF << ($emBits & 7)); + if ((~$maskedDB[0] & $temp) != $temp) { + return false; + } + $dbMask = $this->_mgf1($h, $emLen - $this->hLen - 1); + $db = $maskedDB ^ $dbMask; + $db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0]; + $temp = $emLen - $this->hLen - $sLen - 2; + if (substr($db, 0, $temp) != str_repeat(chr(0), $temp) || ord($db[$temp]) != 1) { + return false; + } + $salt = substr($db, $temp + 1); // should be $sLen long + $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt; + $h2 = $this->hash->hash($m2); + return $h == $h2; + } + + /** + * RSASSA-PSS-SIGN + * + * See {@link http://tools.ietf.org/html/rfc3447#section-8.1.1 RFC3447#section-8.1.1}. + * + * @access private + * @param String $m + * @return String + */ + function _rsassa_pss_sign($m) + { + // EMSA-PSS encoding + + $em = $this->_emsa_pss_encode($m, 8 * $this->k - 1); + + // RSA signature + + $m = $this->_os2ip($em); + $s = $this->_rsasp1($m); + $s = $this->_i2osp($s, $this->k); + + // Output the signature S + + return $s; + } + + /** + * RSASSA-PSS-VERIFY + * + * See {@link http://tools.ietf.org/html/rfc3447#section-8.1.2 RFC3447#section-8.1.2}. + * + * @access private + * @param String $m + * @param String $s + * @return String + */ + function _rsassa_pss_verify($m, $s) + { + // Length checking + + if (strlen($s) != $this->k) { + user_error('Invalid signature', E_USER_NOTICE); + return false; + } + + // RSA verification + + $modBits = 8 * $this->k; + + $s2 = $this->_os2ip($s); + $m2 = $this->_rsavp1($s2); + if ($m2 === false) { + user_error('Invalid signature', E_USER_NOTICE); + return false; + } + $em = $this->_i2osp($m2, $modBits >> 3); + if ($em === false) { + user_error('Invalid signature', E_USER_NOTICE); + return false; + } + + // EMSA-PSS verification + + return $this->_emsa_pss_verify($m, $em, $modBits - 1); + } + + /** + * EMSA-PKCS1-V1_5-ENCODE + * + * See {@link http://tools.ietf.org/html/rfc3447#section-9.2 RFC3447#section-9.2}. + * + * @access private + * @param String $m + * @param Integer $emLen + * @return String + */ + function _emsa_pkcs1_v1_5_encode($m, $emLen) + { + $h = $this->hash->hash($m); + if ($h === false) { + return false; + } + + // see http://tools.ietf.org/html/rfc3447#page-43 + switch ($this->hashName) { + case 'md2': + $t = pack('H*', '3020300c06082a864886f70d020205000410'); + break; + case 'md5': + $t = pack('H*', '3020300c06082a864886f70d020505000410'); + break; + case 'sha1': + $t = pack('H*', '3021300906052b0e03021a05000414'); + break; + case 'sha256': + $t = pack('H*', '3031300d060960864801650304020105000420'); + break; + case 'sha384': + $t = pack('H*', '3041300d060960864801650304020205000430'); + break; + case 'sha512': + $t = pack('H*', '3051300d060960864801650304020305000440'); + } + $t.= $h; + $tLen = strlen($t); + + if ($emLen < $tLen + 11) { + user_error('Intended encoded message length too short', E_USER_NOTICE); + return false; + } + + $ps = str_repeat(chr(0xFF), $emLen - $tLen - 3); + + $em = "\0\1$ps\0$t"; + + return $em; + } + + /** + * RSASSA-PKCS1-V1_5-SIGN + * + * See {@link http://tools.ietf.org/html/rfc3447#section-8.2.1 RFC3447#section-8.2.1}. + * + * @access private + * @param String $m + * @return String + */ + function _rsassa_pkcs1_v1_5_sign($m) + { + // EMSA-PKCS1-v1_5 encoding + + $em = $this->_emsa_pkcs1_v1_5_encode($m, $this->k); + if ($em === false) { + user_error('RSA modulus too short', E_USER_NOTICE); + return false; + } + + // RSA signature + + $m = $this->_os2ip($em); + $s = $this->_rsasp1($m); + $s = $this->_i2osp($s, $this->k); + + // Output the signature S + + return $s; + } + + /** + * RSASSA-PKCS1-V1_5-VERIFY + * + * See {@link http://tools.ietf.org/html/rfc3447#section-8.2.2 RFC3447#section-8.2.2}. + * + * @access private + * @param String $m + * @return String + */ + function _rsassa_pkcs1_v1_5_verify($m, $s) + { + // Length checking + + if (strlen($s) != $this->k) { + user_error('Invalid signature', E_USER_NOTICE); + return false; + } + + // RSA verification + + $s = $this->_os2ip($s); + $m2 = $this->_rsavp1($s); + if ($m2 === false) { + user_error('Invalid signature', E_USER_NOTICE); + return false; + } + $em = $this->_i2osp($m2, $this->k); + if ($em === false) { + user_error('Invalid signature', E_USER_NOTICE); + return false; + } + + // EMSA-PKCS1-v1_5 encoding + + $em2 = $this->_emsa_pkcs1_v1_5_encode($m, $this->k); + if ($em2 === false) { + user_error('RSA modulus too short', E_USER_NOTICE); + return false; + } + + // Compare + + return $em == $em2; + } + + /** + * Set Encryption Mode + * + * Valid values include CRYPT_RSA_ENCRYPTION_OAEP and CRYPT_RSA_ENCRYPTION_PKCS1. + * + * @access public + * @param Integer $mode + */ + function setEncryptionMode($mode) + { + $this->encryptionMode = $mode; + } + + /** + * Set Signature Mode + * + * Valid values include CRYPT_RSA_SIGNATURE_PSS and CRYPT_RSA_SIGNATURE_PKCS1 + * + * @access public + * @param Integer $mode + */ + function setSignatureMode($mode) + { + $this->signatureMode = $mode; + } + + /** + * Encryption + * + * Both CRYPT_RSA_ENCRYPTION_OAEP and CRYPT_RSA_ENCRYPTION_PKCS1 both place limits on how long $plaintext can be. + * If $plaintext exceeds those limits it will be broken up so that it does and the resultant ciphertext's will + * be concatenated together. + * + * @see decrypt() + * @access public + * @param String $plaintext + * @return String + */ + function encrypt($plaintext) + { + switch ($this->encryptionMode) { + case CRYPT_RSA_ENCRYPTION_PKCS1: + $plaintext = str_split($plaintext, $this->k - 11); + $ciphertext = ''; + foreach ($plaintext as $m) { + $ciphertext.= $this->_rsaes_pkcs1_v1_5_encrypt($m); + } + return $ciphertext; + //case CRYPT_RSA_ENCRYPTION_OAEP: + default: + $plaintext = str_split($plaintext, $this->k - 2 * $this->hLen - 2); + $ciphertext = ''; + foreach ($plaintext as $m) { + $ciphertext.= $this->_rsaes_oaep_encrypt($m); + } + return $ciphertext; + } + } + + /** + * Decryption + * + * @see encrypt() + * @access public + * @param String $plaintext + * @return String + */ + function decrypt($ciphertext) + { + switch ($this->encryptionMode) { + case CRYPT_RSA_ENCRYPTION_PKCS1: + $ciphertext = str_split($ciphertext, $this->k); + $plaintext = ''; + foreach ($ciphertext as $c) { + $temp = $this->_rsaes_pkcs1_v1_5_decrypt($c); + if ($temp === false) { + return false; + } + $plaintext.= $temp; + } + return $plaintext; + //case CRYPT_RSA_ENCRYPTION_OAEP: + default: + $ciphertext = str_split($ciphertext, $this->k); + $plaintext = ''; + foreach ($ciphertext as $c) { + $temp = $this->_rsaes_oaep_decrypt($c); + if ($temp === false) { + return false; + } + $plaintext.= $temp; + } + return $plaintext; + } + } + + /** + * Create a signature + * + * @see verify() + * @access public + * @param String $message + * @return String + */ + function sign($message) + { + switch ($this->signatureMode) { + case CRYPT_RSA_SIGNATURE_PKCS1: + return $this->_rsassa_pkcs1_v1_5_sign($message); + //case CRYPT_RSA_SIGNATURE_PSS: + default: + return $this->_rsassa_pss_sign($message); + } + } + + /** + * Verifies a signature + * + * @see sign() + * @access public + * @param String $message + * @param String $signature + * @return Boolean + */ + function verify($message, $signature) + { + switch ($this->signatureMode) { + case CRYPT_RSA_SIGNATURE_PKCS1: + return $this->_rsassa_pkcs1_v1_5_verify($message, $signature); + //case CRYPT_RSA_SIGNATURE_PSS: + default: + return $this->_rsassa_pss_verify($message, $signature); + } + } +} \ No newline at end of file diff --git a/plugins/OStatus/extlib/Crypt/Random.php b/plugins/OStatus/extlib/Crypt/Random.php new file mode 100644 index 000000000..fbb41074e --- /dev/null +++ b/plugins/OStatus/extlib/Crypt/Random.php @@ -0,0 +1,70 @@ + + * + * + * + * LICENSE: This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * @category Crypt + * @package Crypt_Random + * @author Jim Wigginton + * @copyright MMVII Jim Wigginton + * @license http://www.gnu.org/licenses/lgpl.txt + * @version $Id: Random.php,v 1.4 2008/05/21 05:15:32 terrafrost Exp $ + * @link http://phpseclib.sourceforge.net + */ + +/** + * Generate a random value. Feel free to replace this function with a cryptographically secure PRNG. + * + * @param optional Integer $min + * @param optional Integer $max + * @param optional String $randomness_path + * @return Integer + * @access public + */ +function crypt_random($min = 0, $max = 0x7FFFFFFF, $randomness_path = '/dev/urandom') +{ + static $seeded = false; + + if (!$seeded) { + $seeded = true; + if (file_exists($randomness_path)) { + $fp = fopen($randomness_path, 'r'); + $temp = unpack('Nint', fread($fp, 4)); + mt_srand($temp['int']); + fclose($fp); + } else { + list($sec, $usec) = explode(' ', microtime()); + mt_srand((float) $sec + ((float) $usec * 100000)); + } + } + + return mt_rand($min, $max); +} +?> \ No newline at end of file diff --git a/plugins/OStatus/extlib/Crypt/Rijndael.php b/plugins/OStatus/extlib/Crypt/Rijndael.php new file mode 100644 index 000000000..19bba83f3 --- /dev/null +++ b/plugins/OStatus/extlib/Crypt/Rijndael.php @@ -0,0 +1,1135 @@ + + * setKey('abcdefghijklmnop'); + * + * $size = 10 * 1024; + * $plaintext = ''; + * for ($i = 0; $i < $size; $i++) { + * $plaintext.= 'a'; + * } + * + * echo $rijndael->decrypt($rijndael->encrypt($plaintext)); + * ?> + * + * + * LICENSE: This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * @category Crypt + * @package Crypt_Rijndael + * @author Jim Wigginton + * @copyright MMVIII Jim Wigginton + * @license http://www.gnu.org/licenses/lgpl.txt + * @version $Id: Rijndael.php,v 1.8 2009/11/23 19:06:07 terrafrost Exp $ + * @link http://phpseclib.sourceforge.net + */ + +/**#@+ + * @access public + * @see Crypt_Rijndael::encrypt() + * @see Crypt_Rijndael::decrypt() + */ +/** + * Encrypt / decrypt using the Electronic Code Book mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29 + */ +define('CRYPT_RIJNDAEL_MODE_ECB', 1); +/** + * Encrypt / decrypt using the Code Book Chaining mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29 + */ +define('CRYPT_RIJNDAEL_MODE_CBC', 2); +/**#@-*/ + +/**#@+ + * @access private + * @see Crypt_Rijndael::Crypt_Rijndael() + */ +/** + * Toggles the internal implementation + */ +define('CRYPT_RIJNDAEL_MODE_INTERNAL', 1); +/** + * Toggles the mcrypt implementation + */ +define('CRYPT_RIJNDAEL_MODE_MCRYPT', 2); +/**#@-*/ + +/** + * Pure-PHP implementation of Rijndael. + * + * @author Jim Wigginton + * @version 0.1.0 + * @access public + * @package Crypt_Rijndael + */ +class Crypt_Rijndael { + /** + * The Encryption Mode + * + * @see Crypt_Rijndael::Crypt_Rijndael() + * @var Integer + * @access private + */ + var $mode; + + /** + * The Key + * + * @see Crypt_Rijndael::setKey() + * @var String + * @access private + */ + var $key = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; + + /** + * The Initialization Vector + * + * @see Crypt_Rijndael::setIV() + * @var String + * @access private + */ + var $iv = ''; + + /** + * A "sliding" Initialization Vector + * + * @see Crypt_Rijndael::enableContinuousBuffer() + * @var String + * @access private + */ + var $encryptIV = ''; + + /** + * A "sliding" Initialization Vector + * + * @see Crypt_Rijndael::enableContinuousBuffer() + * @var String + * @access private + */ + var $decryptIV = ''; + + /** + * Continuous Buffer status + * + * @see Crypt_Rijndael::enableContinuousBuffer() + * @var Boolean + * @access private + */ + var $continuousBuffer = false; + + /** + * Padding status + * + * @see Crypt_Rijndael::enablePadding() + * @var Boolean + * @access private + */ + var $padding = true; + + /** + * Does the key schedule need to be (re)calculated? + * + * @see setKey() + * @see setBlockLength() + * @see setKeyLength() + * @var Boolean + * @access private + */ + var $changed = true; + + /** + * Has the key length explicitly been set or should it be derived from the key, itself? + * + * @see setKeyLength() + * @var Boolean + * @access private + */ + var $explicit_key_length = false; + + /** + * The Key Schedule + * + * @see _setup() + * @var Array + * @access private + */ + var $w; + + /** + * The Inverse Key Schedule + * + * @see _setup() + * @var Array + * @access private + */ + var $dw; + + /** + * The Block Length + * + * @see setBlockLength() + * @var Integer + * @access private + * @internal The max value is 32, the min value is 16. All valid values are multiples of 4. Exists in conjunction with + * $Nb because we need this value and not $Nb to pad strings appropriately. + */ + var $block_size = 16; + + /** + * The Block Length divided by 32 + * + * @see setBlockLength() + * @var Integer + * @access private + * @internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4. Exists in conjunction with $block_size + * because the encryption / decryption / key schedule creation requires this number and not $block_size. We could + * derive this from $block_size or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu + * of that, we'll just precompute it once. + * + */ + var $Nb = 4; + + /** + * The Key Length + * + * @see setKeyLength() + * @var Integer + * @access private + * @internal The max value is 256 / 8 = 32, the min value is 128 / 8 = 16. Exists in conjunction with $key_size + * because the encryption / decryption / key schedule creation requires this number and not $key_size. We could + * derive this from $key_size or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu + * of that, we'll just precompute it once. + */ + var $key_size = 16; + + /** + * The Key Length divided by 32 + * + * @see setKeyLength() + * @var Integer + * @access private + * @internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4 + */ + var $Nk = 4; + + /** + * The Number of Rounds + * + * @var Integer + * @access private + * @internal The max value is 14, the min value is 10. + */ + var $Nr; + + /** + * Shift offsets + * + * @var Array + * @access private + */ + var $c; + + /** + * Precomputed mixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $t0; + + /** + * Precomputed mixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $t1; + + /** + * Precomputed mixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $t2; + + /** + * Precomputed mixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $t3; + + /** + * Precomputed invMixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $dt0; + + /** + * Precomputed invMixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $dt1; + + /** + * Precomputed invMixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $dt2; + + /** + * Precomputed invMixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $dt3; + + /** + * Default Constructor. + * + * Determines whether or not the mcrypt extension should be used. $mode should only, at present, be + * CRYPT_RIJNDAEL_MODE_ECB or CRYPT_RIJNDAEL_MODE_CBC. If not explictly set, CRYPT_RIJNDAEL_MODE_CBC will be used. + * + * @param optional Integer $mode + * @return Crypt_Rijndael + * @access public + */ + function Crypt_Rijndael($mode = CRYPT_RIJNDAEL_MODE_CBC) + { + switch ($mode) { + case CRYPT_RIJNDAEL_MODE_ECB: + case CRYPT_RIJNDAEL_MODE_CBC: + $this->mode = $mode; + break; + default: + $this->mode = CRYPT_RIJNDAEL_MODE_CBC; + } + + // according to (section 5.2.1), + // precomputed tables can be used in the mixColumns phase. in that example, they're assigned t0...t3, so + // those are the names we'll use. + $this->t3 = array( + 0x6363A5C6, 0x7C7C84F8, 0x777799EE, 0x7B7B8DF6, 0xF2F20DFF, 0x6B6BBDD6, 0x6F6FB1DE, 0xC5C55491, + 0x30305060, 0x01010302, 0x6767A9CE, 0x2B2B7D56, 0xFEFE19E7, 0xD7D762B5, 0xABABE64D, 0x76769AEC, + 0xCACA458F, 0x82829D1F, 0xC9C94089, 0x7D7D87FA, 0xFAFA15EF, 0x5959EBB2, 0x4747C98E, 0xF0F00BFB, + 0xADADEC41, 0xD4D467B3, 0xA2A2FD5F, 0xAFAFEA45, 0x9C9CBF23, 0xA4A4F753, 0x727296E4, 0xC0C05B9B, + 0xB7B7C275, 0xFDFD1CE1, 0x9393AE3D, 0x26266A4C, 0x36365A6C, 0x3F3F417E, 0xF7F702F5, 0xCCCC4F83, + 0x34345C68, 0xA5A5F451, 0xE5E534D1, 0xF1F108F9, 0x717193E2, 0xD8D873AB, 0x31315362, 0x15153F2A, + 0x04040C08, 0xC7C75295, 0x23236546, 0xC3C35E9D, 0x18182830, 0x9696A137, 0x05050F0A, 0x9A9AB52F, + 0x0707090E, 0x12123624, 0x80809B1B, 0xE2E23DDF, 0xEBEB26CD, 0x2727694E, 0xB2B2CD7F, 0x75759FEA, + 0x09091B12, 0x83839E1D, 0x2C2C7458, 0x1A1A2E34, 0x1B1B2D36, 0x6E6EB2DC, 0x5A5AEEB4, 0xA0A0FB5B, + 0x5252F6A4, 0x3B3B4D76, 0xD6D661B7, 0xB3B3CE7D, 0x29297B52, 0xE3E33EDD, 0x2F2F715E, 0x84849713, + 0x5353F5A6, 0xD1D168B9, 0x00000000, 0xEDED2CC1, 0x20206040, 0xFCFC1FE3, 0xB1B1C879, 0x5B5BEDB6, + 0x6A6ABED4, 0xCBCB468D, 0xBEBED967, 0x39394B72, 0x4A4ADE94, 0x4C4CD498, 0x5858E8B0, 0xCFCF4A85, + 0xD0D06BBB, 0xEFEF2AC5, 0xAAAAE54F, 0xFBFB16ED, 0x4343C586, 0x4D4DD79A, 0x33335566, 0x85859411, + 0x4545CF8A, 0xF9F910E9, 0x02020604, 0x7F7F81FE, 0x5050F0A0, 0x3C3C4478, 0x9F9FBA25, 0xA8A8E34B, + 0x5151F3A2, 0xA3A3FE5D, 0x4040C080, 0x8F8F8A05, 0x9292AD3F, 0x9D9DBC21, 0x38384870, 0xF5F504F1, + 0xBCBCDF63, 0xB6B6C177, 0xDADA75AF, 0x21216342, 0x10103020, 0xFFFF1AE5, 0xF3F30EFD, 0xD2D26DBF, + 0xCDCD4C81, 0x0C0C1418, 0x13133526, 0xECEC2FC3, 0x5F5FE1BE, 0x9797A235, 0x4444CC88, 0x1717392E, + 0xC4C45793, 0xA7A7F255, 0x7E7E82FC, 0x3D3D477A, 0x6464ACC8, 0x5D5DE7BA, 0x19192B32, 0x737395E6, + 0x6060A0C0, 0x81819819, 0x4F4FD19E, 0xDCDC7FA3, 0x22226644, 0x2A2A7E54, 0x9090AB3B, 0x8888830B, + 0x4646CA8C, 0xEEEE29C7, 0xB8B8D36B, 0x14143C28, 0xDEDE79A7, 0x5E5EE2BC, 0x0B0B1D16, 0xDBDB76AD, + 0xE0E03BDB, 0x32325664, 0x3A3A4E74, 0x0A0A1E14, 0x4949DB92, 0x06060A0C, 0x24246C48, 0x5C5CE4B8, + 0xC2C25D9F, 0xD3D36EBD, 0xACACEF43, 0x6262A6C4, 0x9191A839, 0x9595A431, 0xE4E437D3, 0x79798BF2, + 0xE7E732D5, 0xC8C8438B, 0x3737596E, 0x6D6DB7DA, 0x8D8D8C01, 0xD5D564B1, 0x4E4ED29C, 0xA9A9E049, + 0x6C6CB4D8, 0x5656FAAC, 0xF4F407F3, 0xEAEA25CF, 0x6565AFCA, 0x7A7A8EF4, 0xAEAEE947, 0x08081810, + 0xBABAD56F, 0x787888F0, 0x25256F4A, 0x2E2E725C, 0x1C1C2438, 0xA6A6F157, 0xB4B4C773, 0xC6C65197, + 0xE8E823CB, 0xDDDD7CA1, 0x74749CE8, 0x1F1F213E, 0x4B4BDD96, 0xBDBDDC61, 0x8B8B860D, 0x8A8A850F, + 0x707090E0, 0x3E3E427C, 0xB5B5C471, 0x6666AACC, 0x4848D890, 0x03030506, 0xF6F601F7, 0x0E0E121C, + 0x6161A3C2, 0x35355F6A, 0x5757F9AE, 0xB9B9D069, 0x86869117, 0xC1C15899, 0x1D1D273A, 0x9E9EB927, + 0xE1E138D9, 0xF8F813EB, 0x9898B32B, 0x11113322, 0x6969BBD2, 0xD9D970A9, 0x8E8E8907, 0x9494A733, + 0x9B9BB62D, 0x1E1E223C, 0x87879215, 0xE9E920C9, 0xCECE4987, 0x5555FFAA, 0x28287850, 0xDFDF7AA5, + 0x8C8C8F03, 0xA1A1F859, 0x89898009, 0x0D0D171A, 0xBFBFDA65, 0xE6E631D7, 0x4242C684, 0x6868B8D0, + 0x4141C382, 0x9999B029, 0x2D2D775A, 0x0F0F111E, 0xB0B0CB7B, 0x5454FCA8, 0xBBBBD66D, 0x16163A2C + ); + + $this->dt3 = array( + 0xF4A75051, 0x4165537E, 0x17A4C31A, 0x275E963A, 0xAB6BCB3B, 0x9D45F11F, 0xFA58ABAC, 0xE303934B, + 0x30FA5520, 0x766DF6AD, 0xCC769188, 0x024C25F5, 0xE5D7FC4F, 0x2ACBD7C5, 0x35448026, 0x62A38FB5, + 0xB15A49DE, 0xBA1B6725, 0xEA0E9845, 0xFEC0E15D, 0x2F7502C3, 0x4CF01281, 0x4697A38D, 0xD3F9C66B, + 0x8F5FE703, 0x929C9515, 0x6D7AEBBF, 0x5259DA95, 0xBE832DD4, 0x7421D358, 0xE0692949, 0xC9C8448E, + 0xC2896A75, 0x8E7978F4, 0x583E6B99, 0xB971DD27, 0xE14FB6BE, 0x88AD17F0, 0x20AC66C9, 0xCE3AB47D, + 0xDF4A1863, 0x1A3182E5, 0x51336097, 0x537F4562, 0x6477E0B1, 0x6BAE84BB, 0x81A01CFE, 0x082B94F9, + 0x48685870, 0x45FD198F, 0xDE6C8794, 0x7BF8B752, 0x73D323AB, 0x4B02E272, 0x1F8F57E3, 0x55AB2A66, + 0xEB2807B2, 0xB5C2032F, 0xC57B9A86, 0x3708A5D3, 0x2887F230, 0xBFA5B223, 0x036ABA02, 0x16825CED, + 0xCF1C2B8A, 0x79B492A7, 0x07F2F0F3, 0x69E2A14E, 0xDAF4CD65, 0x05BED506, 0x34621FD1, 0xA6FE8AC4, + 0x2E539D34, 0xF355A0A2, 0x8AE13205, 0xF6EB75A4, 0x83EC390B, 0x60EFAA40, 0x719F065E, 0x6E1051BD, + 0x218AF93E, 0xDD063D96, 0x3E05AEDD, 0xE6BD464D, 0x548DB591, 0xC45D0571, 0x06D46F04, 0x5015FF60, + 0x98FB2419, 0xBDE997D6, 0x4043CC89, 0xD99E7767, 0xE842BDB0, 0x898B8807, 0x195B38E7, 0xC8EEDB79, + 0x7C0A47A1, 0x420FE97C, 0x841EC9F8, 0x00000000, 0x80868309, 0x2BED4832, 0x1170AC1E, 0x5A724E6C, + 0x0EFFFBFD, 0x8538560F, 0xAED51E3D, 0x2D392736, 0x0FD9640A, 0x5CA62168, 0x5B54D19B, 0x362E3A24, + 0x0A67B10C, 0x57E70F93, 0xEE96D2B4, 0x9B919E1B, 0xC0C54F80, 0xDC20A261, 0x774B695A, 0x121A161C, + 0x93BA0AE2, 0xA02AE5C0, 0x22E0433C, 0x1B171D12, 0x090D0B0E, 0x8BC7ADF2, 0xB6A8B92D, 0x1EA9C814, + 0xF1198557, 0x75074CAF, 0x99DDBBEE, 0x7F60FDA3, 0x01269FF7, 0x72F5BC5C, 0x663BC544, 0xFB7E345B, + 0x4329768B, 0x23C6DCCB, 0xEDFC68B6, 0xE4F163B8, 0x31DCCAD7, 0x63851042, 0x97224013, 0xC6112084, + 0x4A247D85, 0xBB3DF8D2, 0xF93211AE, 0x29A16DC7, 0x9E2F4B1D, 0xB230F3DC, 0x8652EC0D, 0xC1E3D077, + 0xB3166C2B, 0x70B999A9, 0x9448FA11, 0xE9642247, 0xFC8CC4A8, 0xF03F1AA0, 0x7D2CD856, 0x3390EF22, + 0x494EC787, 0x38D1C1D9, 0xCAA2FE8C, 0xD40B3698, 0xF581CFA6, 0x7ADE28A5, 0xB78E26DA, 0xADBFA43F, + 0x3A9DE42C, 0x78920D50, 0x5FCC9B6A, 0x7E466254, 0x8D13C2F6, 0xD8B8E890, 0x39F75E2E, 0xC3AFF582, + 0x5D80BE9F, 0xD0937C69, 0xD52DA96F, 0x2512B3CF, 0xAC993BC8, 0x187DA710, 0x9C636EE8, 0x3BBB7BDB, + 0x267809CD, 0x5918F46E, 0x9AB701EC, 0x4F9AA883, 0x956E65E6, 0xFFE67EAA, 0xBCCF0821, 0x15E8E6EF, + 0xE79BD9BA, 0x6F36CE4A, 0x9F09D4EA, 0xB07CD629, 0xA4B2AF31, 0x3F23312A, 0xA59430C6, 0xA266C035, + 0x4EBC3774, 0x82CAA6FC, 0x90D0B0E0, 0xA7D81533, 0x04984AF1, 0xECDAF741, 0xCD500E7F, 0x91F62F17, + 0x4DD68D76, 0xEFB04D43, 0xAA4D54CC, 0x9604DFE4, 0xD1B5E39E, 0x6A881B4C, 0x2C1FB8C1, 0x65517F46, + 0x5EEA049D, 0x8C355D01, 0x877473FA, 0x0B412EFB, 0x671D5AB3, 0xDBD25292, 0x105633E9, 0xD647136D, + 0xD7618C9A, 0xA10C7A37, 0xF8148E59, 0x133C89EB, 0xA927EECE, 0x61C935B7, 0x1CE5EDE1, 0x47B13C7A, + 0xD2DF599C, 0xF2733F55, 0x14CE7918, 0xC737BF73, 0xF7CDEA53, 0xFDAA5B5F, 0x3D6F14DF, 0x44DB8678, + 0xAFF381CA, 0x68C43EB9, 0x24342C38, 0xA3405FC2, 0x1DC37216, 0xE2250CBC, 0x3C498B28, 0x0D9541FF, + 0xA8017139, 0x0CB3DE08, 0xB4E49CD8, 0x56C19064, 0xCB84617B, 0x32B670D5, 0x6C5C7448, 0xB85742D0 + ); + + for ($i = 0; $i < 256; $i++) { + $this->t2[$i << 8] = (($this->t3[$i] << 8) & 0xFFFFFF00) | (($this->t3[$i] >> 24) & 0x000000FF); + $this->t1[$i << 16] = (($this->t3[$i] << 16) & 0xFFFF0000) | (($this->t3[$i] >> 16) & 0x0000FFFF); + $this->t0[$i << 24] = (($this->t3[$i] << 24) & 0xFF000000) | (($this->t3[$i] >> 8) & 0x00FFFFFF); + + $this->dt2[$i << 8] = (($this->dt3[$i] << 8) & 0xFFFFFF00) | (($this->dt3[$i] >> 24) & 0x000000FF); + $this->dt1[$i << 16] = (($this->dt3[$i] << 16) & 0xFFFF0000) | (($this->dt3[$i] >> 16) & 0x0000FFFF); + $this->dt0[$i << 24] = (($this->dt3[$i] << 24) & 0xFF000000) | (($this->dt3[$i] >> 8) & 0x00FFFFFF); + } + } + + /** + * Sets the key. + * + * Keys can be of any length. Rijndael, itself, requires the use of a key that's between 128-bits and 256-bits long and + * whose length is a multiple of 32. If the key is less than 256-bits and the key length isn't set, we round the length + * up to the closest valid key length, padding $key with null bytes. If the key is more than 256-bits, we trim the + * excess bits. + * + * If the key is not explicitly set, it'll be assumed to be all null bytes. + * + * @access public + * @param String $key + */ + function setKey($key) + { + $this->key = $key; + $this->changed = true; + } + + /** + * Sets the initialization vector. (optional) + * + * SetIV is not required when CRYPT_RIJNDAEL_MODE_ECB is being used. If not explictly set, it'll be assumed + * to be all zero's. + * + * @access public + * @param String $iv + */ + function setIV($iv) + { + $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($iv, 0, $this->block_size), $this->block_size, chr(0));; + } + + /** + * Sets the key length + * + * Valid key lengths are 128, 160, 192, 224, and 256. If the length is less than 128, it will be rounded up to + * 128. If the length is greater then 128 and invalid, it will be rounded down to the closest valid amount. + * + * @access public + * @param Integer $length + */ + function setKeyLength($length) + { + $length >>= 5; + if ($length > 8) { + $length = 8; + } else if ($length < 4) { + $length = 4; + } + $this->Nk = $length; + $this->key_size = $length << 2; + + $this->explicit_key_length = true; + $this->changed = true; + } + + /** + * Sets the block length + * + * Valid block lengths are 128, 160, 192, 224, and 256. If the length is less than 128, it will be rounded up to + * 128. If the length is greater then 128 and invalid, it will be rounded down to the closest valid amount. + * + * @access public + * @param Integer $length + */ + function setBlockLength($length) + { + $length >>= 5; + if ($length > 8) { + $length = 8; + } else if ($length < 4) { + $length = 4; + } + $this->Nb = $length; + $this->block_size = $length << 2; + $this->changed = true; + } + + /** + * Encrypts a message. + * + * $plaintext will be padded with additional bytes such that it's length is a multiple of the block size. Other Rjindael + * implementations may or may not pad in the same manner. Other common approaches to padding and the reasons why it's + * necessary are discussed in the following + * URL: + * + * {@link http://www.di-mgt.com.au/cryptopad.html http://www.di-mgt.com.au/cryptopad.html} + * + * An alternative to padding is to, separately, send the length of the file. This is what SSH, in fact, does. + * strlen($plaintext) will still need to be a multiple of 8, however, arbitrary values can be added to make it that + * length. + * + * @see Crypt_Rijndael::decrypt() + * @access public + * @param String $plaintext + */ + function encrypt($plaintext) + { + $this->_setup(); + $plaintext = $this->_pad($plaintext); + + $ciphertext = ''; + switch ($this->mode) { + case CRYPT_RIJNDAEL_MODE_ECB: + for ($i = 0; $i < strlen($plaintext); $i+=$this->block_size) { + $ciphertext.= $this->_encryptBlock(substr($plaintext, $i, $this->block_size)); + } + break; + case CRYPT_RIJNDAEL_MODE_CBC: + $xor = $this->encryptIV; + for ($i = 0; $i < strlen($plaintext); $i+=$this->block_size) { + $block = substr($plaintext, $i, $this->block_size); + $block = $this->_encryptBlock($block ^ $xor); + $xor = $block; + $ciphertext.= $block; + } + if ($this->continuousBuffer) { + $this->encryptIV = $xor; + } + } + + return $ciphertext; + } + + /** + * Decrypts a message. + * + * If strlen($ciphertext) is not a multiple of the block size, null bytes will be added to the end of the string until + * it is. + * + * @see Crypt_Rijndael::encrypt() + * @access public + * @param String $ciphertext + */ + function decrypt($ciphertext) + { + $this->_setup(); + // we pad with chr(0) since that's what mcrypt_generic does. to quote from http://php.net/function.mcrypt-generic : + // "The data is padded with "\0" to make sure the length of the data is n * blocksize." + $ciphertext = str_pad($ciphertext, (strlen($ciphertext) + $this->block_size - 1) % $this->block_size, chr(0)); + + $plaintext = ''; + switch ($this->mode) { + case CRYPT_RIJNDAEL_MODE_ECB: + for ($i = 0; $i < strlen($ciphertext); $i+=$this->block_size) { + $plaintext.= $this->_decryptBlock(substr($ciphertext, $i, $this->block_size)); + } + break; + case CRYPT_RIJNDAEL_MODE_CBC: + $xor = $this->decryptIV; + for ($i = 0; $i < strlen($ciphertext); $i+=$this->block_size) { + $block = substr($ciphertext, $i, $this->block_size); + $plaintext.= $this->_decryptBlock($block) ^ $xor; + $xor = $block; + } + if ($this->continuousBuffer) { + $this->decryptIV = $xor; + } + } + + return $this->_unpad($plaintext); + } + + /** + * Encrypts a block + * + * @access private + * @param String $in + * @return String + */ + function _encryptBlock($in) + { + $state = array(); + $words = unpack('N*word', $in); + + // addRoundKey + foreach ($words as $word) { + $state[] = $word ^ $this->w[0][count($state)]; + } + + // fips-197.pdf#page=19, "Figure 5. Pseudo Code for the Cipher", states that this loop has four components - + // subBytes, shiftRows, mixColumns, and addRoundKey. fips-197.pdf#page=30, "Implementation Suggestions Regarding + // Various Platforms" suggests that performs enhanced implementations are described in Rijndael-ammended.pdf. + // Rijndael-ammended.pdf#page=20, "Implementation aspects / 32-bit processor", discusses such an optimization. + // Unfortunately, the description given there is not quite correct. Per aes.spec.v316.pdf#page=19 [1], + // equation (7.4.7) is supposed to use addition instead of subtraction, so we'll do that here, as well. + + // [1] http://fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.v316.pdf + $temp = array(); + for ($round = 1; $round < $this->Nr; $round++) { + $i = 0; // $this->c[0] == 0 + $j = $this->c[1]; + $k = $this->c[2]; + $l = $this->c[3]; + + while ($i < $this->Nb) { + $temp[$i] = $this->t0[$state[$i] & 0xFF000000] ^ + $this->t1[$state[$j] & 0x00FF0000] ^ + $this->t2[$state[$k] & 0x0000FF00] ^ + $this->t3[$state[$l] & 0x000000FF] ^ + $this->w[$round][$i]; + $i++; + $j = ($j + 1) % $this->Nb; + $k = ($k + 1) % $this->Nb; + $l = ($l + 1) % $this->Nb; + } + + for ($i = 0; $i < $this->Nb; $i++) { + $state[$i] = $temp[$i]; + } + } + + // subWord + for ($i = 0; $i < $this->Nb; $i++) { + $state[$i] = $this->_subWord($state[$i]); + } + + // shiftRows + addRoundKey + $i = 0; // $this->c[0] == 0 + $j = $this->c[1]; + $k = $this->c[2]; + $l = $this->c[3]; + while ($i < $this->Nb) { + $temp[$i] = ($state[$i] & 0xFF000000) ^ + ($state[$j] & 0x00FF0000) ^ + ($state[$k] & 0x0000FF00) ^ + ($state[$l] & 0x000000FF) ^ + $this->w[$this->Nr][$i]; + $i++; + $j = ($j + 1) % $this->Nb; + $k = ($k + 1) % $this->Nb; + $l = ($l + 1) % $this->Nb; + } + $state = $temp; + + array_unshift($state, 'N*'); + + return call_user_func_array('pack', $state); + } + + /** + * Decrypts a block + * + * @access private + * @param String $in + * @return String + */ + function _decryptBlock($in) + { + $state = array(); + $words = unpack('N*word', $in); + + // addRoundKey + foreach ($words as $word) { + $state[] = $word ^ $this->dw[0][count($state)]; + } + + $temp = array(); + for ($round = $this->Nr - 1; $round > 0; $round--) { + $i = 0; // $this->c[0] == 0 + $j = $this->Nb - $this->c[1]; + $k = $this->Nb - $this->c[2]; + $l = $this->Nb - $this->c[3]; + + while ($i < $this->Nb) { + $temp[$i] = $this->dt0[$state[$i] & 0xFF000000] ^ + $this->dt1[$state[$j] & 0x00FF0000] ^ + $this->dt2[$state[$k] & 0x0000FF00] ^ + $this->dt3[$state[$l] & 0x000000FF] ^ + $this->dw[$round][$i]; + $i++; + $j = ($j + 1) % $this->Nb; + $k = ($k + 1) % $this->Nb; + $l = ($l + 1) % $this->Nb; + } + + for ($i = 0; $i < $this->Nb; $i++) { + $state[$i] = $temp[$i]; + } + } + + // invShiftRows + invSubWord + addRoundKey + $i = 0; // $this->c[0] == 0 + $j = $this->Nb - $this->c[1]; + $k = $this->Nb - $this->c[2]; + $l = $this->Nb - $this->c[3]; + + while ($i < $this->Nb) { + $temp[$i] = $this->dw[0][$i] ^ + $this->_invSubWord(($state[$i] & 0xFF000000) | + ($state[$j] & 0x00FF0000) | + ($state[$k] & 0x0000FF00) | + ($state[$l] & 0x000000FF)); + $i++; + $j = ($j + 1) % $this->Nb; + $k = ($k + 1) % $this->Nb; + $l = ($l + 1) % $this->Nb; + } + + $state = $temp; + + array_unshift($state, 'N*'); + + return call_user_func_array('pack', $state); + } + + /** + * Setup Rijndael + * + * Validates all the variables and calculates $Nr - the number of rounds that need to be performed - and $w - the key + * key schedule. + * + * @access private + */ + function _setup() + { + // Each number in $rcon is equal to the previous number multiplied by two in Rijndael's finite field. + // See http://en.wikipedia.org/wiki/Finite_field_arithmetic#Multiplicative_inverse + static $rcon = array(0, + 0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, + 0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000, + 0x6C000000, 0xD8000000, 0xAB000000, 0x4D000000, 0x9A000000, + 0x2F000000, 0x5E000000, 0xBC000000, 0x63000000, 0xC6000000, + 0x97000000, 0x35000000, 0x6A000000, 0xD4000000, 0xB3000000, + 0x7D000000, 0xFA000000, 0xEF000000, 0xC5000000, 0x91000000 + ); + + if (!$this->changed) { + return; + } + + if (!$this->explicit_key_length) { + // we do >> 2, here, and not >> 5, as we do above, since strlen($this->key) tells us the number of bytes - not bits + $length = strlen($this->key) >> 2; + if ($length > 8) { + $length = 8; + } else if ($length < 4) { + $length = 4; + } + $this->Nk = $length; + $this->key_size = $length << 2; + } + + $this->key = str_pad(substr($this->key, 0, $this->key_size), $this->key_size, chr(0)); + $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($this->iv, 0, $this->block_size), $this->block_size, chr(0)); + + // see Rijndael-ammended.pdf#page=44 + $this->Nr = max($this->Nk, $this->Nb) + 6; + + // shift offsets for Nb = 5, 7 are defined in Rijndael-ammended.pdf#page=44, + // "Table 8: Shift offsets in Shiftrow for the alternative block lengths" + // shift offsets for Nb = 4, 6, 8 are defined in Rijndael-ammended.pdf#page=14, + // "Table 2: Shift offsets for different block lengths" + switch ($this->Nb) { + case 4: + case 5: + case 6: + $this->c = array(0, 1, 2, 3); + break; + case 7: + $this->c = array(0, 1, 2, 4); + break; + case 8: + $this->c = array(0, 1, 3, 4); + } + + $key = $this->key; + + $w = array_values(unpack('N*words', $key)); + + $length = $this->Nb * ($this->Nr + 1); + for ($i = $this->Nk; $i < $length; $i++) { + $temp = $w[$i - 1]; + if ($i % $this->Nk == 0) { + // according to , "the size of an integer is platform-dependent". + // on a 32-bit machine, it's 32-bits, and on a 64-bit machine, it's 64-bits. on a 32-bit machine, + // 0xFFFFFFFF << 8 == 0xFFFFFF00, but on a 64-bit machine, it equals 0xFFFFFFFF00. as such, doing 'and' + // with 0xFFFFFFFF (or 0xFFFFFF00) on a 32-bit machine is unnecessary, but on a 64-bit machine, it is. + $temp = (($temp << 8) & 0xFFFFFF00) | (($temp >> 24) & 0x000000FF); // rotWord + $temp = $this->_subWord($temp) ^ $rcon[$i / $this->Nk]; + } else if ($this->Nk > 6 && $i % $this->Nk == 4) { + $temp = $this->_subWord($temp); + } + $w[$i] = $w[$i - $this->Nk] ^ $temp; + } + + // convert the key schedule from a vector of $Nb * ($Nr + 1) length to a matrix with $Nr + 1 rows and $Nb columns + // and generate the inverse key schedule. more specifically, + // according to (section 5.3.3), + // "The key expansion for the Inverse Cipher is defined as follows: + // 1. Apply the Key Expansion. + // 2. Apply InvMixColumn to all Round Keys except the first and the last one." + // also, see fips-197.pdf#page=27, "5.3.5 Equivalent Inverse Cipher" + $temp = array(); + for ($i = $row = $col = 0; $i < $length; $i++, $col++) { + if ($col == $this->Nb) { + if ($row == 0) { + $this->dw[0] = $this->w[0]; + } else { + // subWord + invMixColumn + invSubWord = invMixColumn + $j = 0; + while ($j < $this->Nb) { + $dw = $this->_subWord($this->w[$row][$j]); + $temp[$j] = $this->dt0[$dw & 0xFF000000] ^ + $this->dt1[$dw & 0x00FF0000] ^ + $this->dt2[$dw & 0x0000FF00] ^ + $this->dt3[$dw & 0x000000FF]; + $j++; + } + $this->dw[$row] = $temp; + } + + $col = 0; + $row++; + } + $this->w[$row][$col] = $w[$i]; + } + + $this->dw[$row] = $this->w[$row]; + + $this->changed = false; + } + + /** + * Performs S-Box substitutions + * + * @access private + */ + function _subWord($word) + { + static $sbox0, $sbox1, $sbox2, $sbox3; + + if (empty($sbox0)) { + $sbox0 = array( + 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, + 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, + 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, + 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, + 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, + 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, + 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, + 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, + 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, + 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, + 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, + 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, + 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, + 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, + 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, + 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16 + ); + + $sbox1 = array(); + $sbox2 = array(); + $sbox3 = array(); + + for ($i = 0; $i < 256; $i++) { + $sbox1[$i << 8] = $sbox0[$i] << 8; + $sbox2[$i << 16] = $sbox0[$i] << 16; + $sbox3[$i << 24] = $sbox0[$i] << 24; + } + } + + return $sbox0[$word & 0x000000FF] | + $sbox1[$word & 0x0000FF00] | + $sbox2[$word & 0x00FF0000] | + $sbox3[$word & 0xFF000000]; + } + + /** + * Performs inverse S-Box substitutions + * + * @access private + */ + function _invSubWord($word) + { + static $sbox0, $sbox1, $sbox2, $sbox3; + + if (empty($sbox0)) { + $sbox0 = array( + 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, + 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, + 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, + 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, + 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, + 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, + 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06, + 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, + 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, + 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, + 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, + 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, + 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, + 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, + 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D + ); + + $sbox1 = array(); + $sbox2 = array(); + $sbox3 = array(); + + for ($i = 0; $i < 256; $i++) { + $sbox1[$i << 8] = $sbox0[$i] << 8; + $sbox2[$i << 16] = $sbox0[$i] << 16; + $sbox3[$i << 24] = $sbox0[$i] << 24; + } + } + + return $sbox0[$word & 0x000000FF] | + $sbox1[$word & 0x0000FF00] | + $sbox2[$word & 0x00FF0000] | + $sbox3[$word & 0xFF000000]; + } + + /** + * Pad "packets". + * + * Rijndael works by encrypting between sixteen and thirty-two bytes at a time, provided that number is also a multiple + * of four. If you ever need to encrypt or decrypt something that isn't of the proper length, it becomes necessary to + * pad the input so that it is of the proper length. + * + * Padding is enabled by default. Sometimes, however, it is undesirable to pad strings. Such is the case in SSH, + * where "packets" are padded with random bytes before being encrypted. Unpad these packets and you risk stripping + * away characters that shouldn't be stripped away. (SSH knows how many bytes are added because the length is + * transmitted separately) + * + * @see Crypt_Rijndael::disablePadding() + * @access public + */ + function enablePadding() + { + $this->padding = true; + } + + /** + * Do not pad packets. + * + * @see Crypt_Rijndael::enablePadding() + * @access public + */ + function disablePadding() + { + $this->padding = false; + } + + /** + * Pads a string + * + * Pads a string using the RSA PKCS padding standards so that its length is a multiple of the blocksize. + * $block_size - (strlen($text) % $block_size) bytes are added, each of which is equal to + * chr($block_size - (strlen($text) % $block_size) + * + * If padding is disabled and $text is not a multiple of the blocksize, the string will be padded regardless + * and padding will, hence forth, be enabled. + * + * @see Crypt_Rijndael::_unpad() + * @access private + */ + function _pad($text) + { + $length = strlen($text); + + if (!$this->padding) { + if ($length % $this->block_size == 0) { + return $text; + } else { + user_error("The plaintext's length ($length) is not a multiple of the block size ({$this->block_size})", E_USER_NOTICE); + $this->padding = true; + } + } + + $pad = $this->block_size - ($length % $this->block_size); + + return str_pad($text, $length + $pad, chr($pad)); + } + + /** + * Unpads a string. + * + * If padding is enabled and the reported padding length is invalid, padding will be, hence forth, disabled. + * + * @see Crypt_Rijndael::_pad() + * @access private + */ + function _unpad($text) + { + if (!$this->padding) { + return $text; + } + + $length = ord($text[strlen($text) - 1]); + + if (!$length || $length > $this->block_size) { + user_error("The number of bytes reported as being padded ($length) is invalid (block size = {$this->block_size})", E_USER_NOTICE); + $this->padding = false; + return $text; + } + + return substr($text, 0, -$length); + } + + /** + * Treat consecutive "packets" as if they are a continuous buffer. + * + * Say you have a 32-byte plaintext $plaintext. Using the default behavior, the two following code snippets + * will yield different outputs: + * + * + * echo $rijndael->encrypt(substr($plaintext, 0, 16)); + * echo $rijndael->encrypt(substr($plaintext, 16, 16)); + * + * + * echo $rijndael->encrypt($plaintext); + * + * + * The solution is to enable the continuous buffer. Although this will resolve the above discrepancy, it creates + * another, as demonstrated with the following: + * + * + * $rijndael->encrypt(substr($plaintext, 0, 16)); + * echo $rijndael->decrypt($des->encrypt(substr($plaintext, 16, 16))); + * + * + * echo $rijndael->decrypt($des->encrypt(substr($plaintext, 16, 16))); + * + * + * With the continuous buffer disabled, these would yield the same output. With it enabled, they yield different + * outputs. The reason is due to the fact that the initialization vector's change after every encryption / + * decryption round when the continuous buffer is enabled. When it's disabled, they remain constant. + * + * Put another way, when the continuous buffer is enabled, the state of the Crypt_Rijndael() object changes after each + * encryption / decryption round, whereas otherwise, it'd remain constant. For this reason, it's recommended that + * continuous buffers not be used. They do offer better security and are, in fact, sometimes required (SSH uses them), + * however, they are also less intuitive and more likely to cause you problems. + * + * @see Crypt_Rijndael::disableContinuousBuffer() + * @access public + */ + function enableContinuousBuffer() + { + $this->continuousBuffer = true; + } + + /** + * Treat consecutive packets as if they are a discontinuous buffer. + * + * The default behavior. + * + * @see Crypt_Rijndael::enableContinuousBuffer() + * @access public + */ + function disableContinuousBuffer() + { + $this->continuousBuffer = false; + $this->encryptIV = $this->iv; + $this->decryptIV = $this->iv; + } + + /** + * String Shift + * + * Inspired by array_shift + * + * @param String $string + * @param optional Integer $index + * @return String + * @access private + */ + function _string_shift(&$string, $index = 1) + { + $substr = substr($string, 0, $index); + $string = substr($string, $index); + return $substr; + } +} + +// vim: ts=4:sw=4:et: +// vim6: fdl=1: \ No newline at end of file diff --git a/plugins/OStatus/extlib/Crypt/TripleDES.php b/plugins/OStatus/extlib/Crypt/TripleDES.php new file mode 100644 index 000000000..03050e5d6 --- /dev/null +++ b/plugins/OStatus/extlib/Crypt/TripleDES.php @@ -0,0 +1,603 @@ + + * setKey('abcdefghijklmnopqrstuvwx'); + * + * $size = 10 * 1024; + * $plaintext = ''; + * for ($i = 0; $i < $size; $i++) { + * $plaintext.= 'a'; + * } + * + * echo $des->decrypt($des->encrypt($plaintext)); + * ?> + * + * + * LICENSE: This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * @category Crypt + * @package Crypt_TripleDES + * @author Jim Wigginton + * @copyright MMVII Jim Wigginton + * @license http://www.gnu.org/licenses/lgpl.txt + * @version $Id: TripleDES.php,v 1.9 2009/11/23 19:06:07 terrafrost Exp $ + * @link http://phpseclib.sourceforge.net + */ + +/** + * Include Crypt_DES + */ +require_once 'DES.php'; + +/** + * Encrypt / decrypt using inner chaining + * + * Inner chaining is used by SSH-1 and is generally considered to be less secure then outer chaining (CRYPT_DES_MODE_CBC3). + */ +define('CRYPT_DES_MODE_3CBC', 3); + +/** + * Encrypt / decrypt using outer chaining + * + * Outer chaining is used by SSH-2 and when the mode is set to CRYPT_DES_MODE_CBC. + */ +define('CRYPT_DES_MODE_CBC3', CRYPT_DES_MODE_CBC); + +/** + * Pure-PHP implementation of Triple DES. + * + * @author Jim Wigginton + * @version 0.1.0 + * @access public + * @package Crypt_TerraDES + */ +class Crypt_TripleDES { + /** + * The Three Keys + * + * @see Crypt_TripleDES::setKey() + * @var String + * @access private + */ + var $key = "\0\0\0\0\0\0\0\0"; + + /** + * The Encryption Mode + * + * @see Crypt_TripleDES::Crypt_TripleDES() + * @var Integer + * @access private + */ + var $mode = CRYPT_DES_MODE_CBC; + + /** + * Continuous Buffer status + * + * @see Crypt_TripleDES::enableContinuousBuffer() + * @var Boolean + * @access private + */ + var $continuousBuffer = false; + + /** + * Padding status + * + * @see Crypt_TripleDES::enablePadding() + * @var Boolean + * @access private + */ + var $padding = true; + + /** + * The Initialization Vector + * + * @see Crypt_TripleDES::setIV() + * @var String + * @access private + */ + var $iv = "\0\0\0\0\0\0\0\0"; + + /** + * A "sliding" Initialization Vector + * + * @see Crypt_TripleDES::enableContinuousBuffer() + * @var String + * @access private + */ + var $encryptIV = "\0\0\0\0\0\0\0\0"; + + /** + * A "sliding" Initialization Vector + * + * @see Crypt_TripleDES::enableContinuousBuffer() + * @var String + * @access private + */ + var $decryptIV = "\0\0\0\0\0\0\0\0"; + + /** + * MCrypt parameters + * + * @see Crypt_TripleDES::setMCrypt() + * @var Array + * @access private + */ + var $mcrypt = array('', ''); + + /** + * The Crypt_DES objects + * + * @var Array + * @access private + */ + var $des; + + /** + * Default Constructor. + * + * Determines whether or not the mcrypt extension should be used. $mode should only, at present, be + * CRYPT_DES_MODE_ECB or CRYPT_DES_MODE_CBC. If not explictly set, CRYPT_DES_MODE_CBC will be used. + * + * @param optional Integer $mode + * @return Crypt_TripleDES + * @access public + */ + function Crypt_TripleDES($mode = CRYPT_DES_MODE_CBC) + { + if ( !defined('CRYPT_DES_MODE') ) { + switch (true) { + case extension_loaded('mcrypt'): + // i'd check to see if des was supported, by doing in_array('des', mcrypt_list_algorithms('')), + // but since that can be changed after the object has been created, there doesn't seem to be + // a lot of point... + define('CRYPT_DES_MODE', CRYPT_DES_MODE_MCRYPT); + break; + default: + define('CRYPT_DES_MODE', CRYPT_DES_MODE_INTERNAL); + } + } + + if ( $mode == CRYPT_DES_MODE_3CBC ) { + $this->mode = CRYPT_DES_MODE_3CBC; + $this->des = array( + new Crypt_DES(CRYPT_DES_MODE_CBC), + new Crypt_DES(CRYPT_DES_MODE_CBC), + new Crypt_DES(CRYPT_DES_MODE_CBC) + ); + + // we're going to be doing the padding, ourselves, so disable it in the Crypt_DES objects + $this->des[0]->disablePadding(); + $this->des[1]->disablePadding(); + $this->des[2]->disablePadding(); + + return; + } + + switch ( CRYPT_DES_MODE ) { + case CRYPT_DES_MODE_MCRYPT: + switch ($mode) { + case CRYPT_DES_MODE_ECB: + $this->mode = MCRYPT_MODE_ECB; break; + case CRYPT_DES_MODE_CBC: + default: + $this->mode = MCRYPT_MODE_CBC; + } + + break; + default: + $this->des = array( + new Crypt_DES(CRYPT_DES_MODE_ECB), + new Crypt_DES(CRYPT_DES_MODE_ECB), + new Crypt_DES(CRYPT_DES_MODE_ECB) + ); + + // we're going to be doing the padding, ourselves, so disable it in the Crypt_DES objects + $this->des[0]->disablePadding(); + $this->des[1]->disablePadding(); + $this->des[2]->disablePadding(); + + switch ($mode) { + case CRYPT_DES_MODE_ECB: + case CRYPT_DES_MODE_CBC: + $this->mode = $mode; + break; + default: + $this->mode = CRYPT_DES_MODE_CBC; + } + } + } + + /** + * Sets the key. + * + * Keys can be of any length. Triple DES, itself, can use 128-bit (eg. strlen($key) == 16) or + * 192-bit (eg. strlen($key) == 24) keys. This function pads and truncates $key as appropriate. + * + * DES also requires that every eighth bit be a parity bit, however, we'll ignore that. + * + * If the key is not explicitly set, it'll be assumed to be all zero's. + * + * @access public + * @param String $key + */ + function setKey($key) + { + $length = strlen($key); + if ($length > 8) { + $key = str_pad($key, 24, chr(0)); + // if $key is between 64 and 128-bits, use the first 64-bits as the last, per this: + // http://php.net/function.mcrypt-encrypt#47973 + $key = $length <= 16 ? substr_replace($key, substr($key, 0, 8), 16) : substr($key, 0, 24); + } + $this->key = $key; + switch (true) { + case CRYPT_DES_MODE == CRYPT_DES_MODE_INTERNAL: + case $this->mode == CRYPT_DES_MODE_3CBC: + $this->des[0]->setKey(substr($key, 0, 8)); + $this->des[1]->setKey(substr($key, 8, 8)); + $this->des[2]->setKey(substr($key, 16, 8)); + } + } + + /** + * Sets the initialization vector. (optional) + * + * SetIV is not required when CRYPT_DES_MODE_ECB is being used. If not explictly set, it'll be assumed + * to be all zero's. + * + * @access public + * @param String $iv + */ + function setIV($iv) + { + $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($iv, 0, 8), 8, chr(0)); + if ($this->mode == CRYPT_DES_MODE_3CBC) { + $this->des[0]->setIV($iv); + $this->des[1]->setIV($iv); + $this->des[2]->setIV($iv); + } + } + + /** + * Sets MCrypt parameters. (optional) + * + * If MCrypt is being used, empty strings will be used, unless otherwise specified. + * + * @link http://php.net/function.mcrypt-module-open#function.mcrypt-module-open + * @access public + * @param optional Integer $algorithm_directory + * @param optional Integer $mode_directory + */ + function setMCrypt($algorithm_directory = '', $mode_directory = '') + { + $this->mcrypt = array($algorithm_directory, $mode_directory); + if ( $this->mode == CRYPT_DES_MODE_3CBC ) { + $this->des[0]->setMCrypt($algorithm_directory, $mode_directory); + $this->des[1]->setMCrypt($algorithm_directory, $mode_directory); + $this->des[2]->setMCrypt($algorithm_directory, $mode_directory); + } + } + + /** + * Encrypts a message. + * + * @access public + * @param String $plaintext + */ + function encrypt($plaintext) + { + $plaintext = $this->_pad($plaintext); + + // if the key is smaller then 8, do what we'd normally do + if ($this->mode == CRYPT_DES_MODE_3CBC && strlen($this->key) > 8) { + $ciphertext = $this->des[2]->encrypt($this->des[1]->decrypt($this->des[0]->encrypt($plaintext))); + + return $ciphertext; + } + + if ( CRYPT_DES_MODE == CRYPT_DES_MODE_MCRYPT ) { + $td = mcrypt_module_open(MCRYPT_3DES, $this->mcrypt[0], $this->mode, $this->mcrypt[1]); + mcrypt_generic_init($td, $this->key, $this->encryptIV); + + $ciphertext = mcrypt_generic($td, $plaintext); + + mcrypt_generic_deinit($td); + mcrypt_module_close($td); + + if ($this->continuousBuffer) { + $this->encryptIV = substr($ciphertext, -8); + } + + return $ciphertext; + } + + if (strlen($this->key) <= 8) { + $this->des[0]->mode = $this->mode; + + return $this->des[0]->encrypt($plaintext); + } + + // we pad with chr(0) since that's what mcrypt_generic does. to quote from http://php.net/function.mcrypt-generic : + // "The data is padded with "\0" to make sure the length of the data is n * blocksize." + $plaintext = str_pad($plaintext, ceil(strlen($plaintext) / 8) * 8, chr(0)); + + $ciphertext = ''; + switch ($this->mode) { + case CRYPT_DES_MODE_ECB: + for ($i = 0; $i < strlen($plaintext); $i+=8) { + $block = substr($plaintext, $i, 8); + $block = $this->des[0]->_processBlock($block, CRYPT_DES_ENCRYPT); + $block = $this->des[1]->_processBlock($block, CRYPT_DES_DECRYPT); + $block = $this->des[2]->_processBlock($block, CRYPT_DES_ENCRYPT); + $ciphertext.= $block; + } + break; + case CRYPT_DES_MODE_CBC: + $xor = $this->encryptIV; + for ($i = 0; $i < strlen($plaintext); $i+=8) { + $block = substr($plaintext, $i, 8) ^ $xor; + $block = $this->des[0]->_processBlock($block, CRYPT_DES_ENCRYPT); + $block = $this->des[1]->_processBlock($block, CRYPT_DES_DECRYPT); + $block = $this->des[2]->_processBlock($block, CRYPT_DES_ENCRYPT); + $xor = $block; + $ciphertext.= $block; + } + if ($this->continuousBuffer) { + $this->encryptIV = $xor; + } + } + + return $ciphertext; + } + + /** + * Decrypts a message. + * + * @access public + * @param String $ciphertext + */ + function decrypt($ciphertext) + { + if ($this->mode == CRYPT_DES_MODE_3CBC && strlen($this->key) > 8) { + $plaintext = $this->des[0]->decrypt($this->des[1]->encrypt($this->des[2]->decrypt($ciphertext))); + + return $this->_unpad($plaintext); + } + + // we pad with chr(0) since that's what mcrypt_generic does. to quote from http://php.net/function.mcrypt-generic : + // "The data is padded with "\0" to make sure the length of the data is n * blocksize." + $ciphertext = str_pad($ciphertext, (strlen($ciphertext) + 7) & 0xFFFFFFF8, chr(0)); + + if ( CRYPT_DES_MODE == CRYPT_DES_MODE_MCRYPT ) { + $td = mcrypt_module_open(MCRYPT_3DES, $this->mcrypt[0], $this->mode, $this->mcrypt[1]); + mcrypt_generic_init($td, $this->key, $this->decryptIV); + + $plaintext = mdecrypt_generic($td, $ciphertext); + + mcrypt_generic_deinit($td); + mcrypt_module_close($td); + + if ($this->continuousBuffer) { + $this->decryptIV = substr($ciphertext, -8); + } + + return $this->_unpad($plaintext); + } + + if (strlen($this->key) <= 8) { + $this->des[0]->mode = $this->mode; + + return $this->_unpad($this->des[0]->decrypt($plaintext)); + } + + $plaintext = ''; + switch ($this->mode) { + case CRYPT_DES_MODE_ECB: + for ($i = 0; $i < strlen($ciphertext); $i+=8) { + $block = substr($ciphertext, $i, 8); + $block = $this->des[2]->_processBlock($block, CRYPT_DES_DECRYPT); + $block = $this->des[1]->_processBlock($block, CRYPT_DES_ENCRYPT); + $block = $this->des[0]->_processBlock($block, CRYPT_DES_DECRYPT); + $plaintext.= $block; + } + break; + case CRYPT_DES_MODE_CBC: + $xor = $this->decryptIV; + for ($i = 0; $i < strlen($ciphertext); $i+=8) { + $orig = $block = substr($ciphertext, $i, 8); + $block = $this->des[2]->_processBlock($block, CRYPT_DES_DECRYPT); + $block = $this->des[1]->_processBlock($block, CRYPT_DES_ENCRYPT); + $block = $this->des[0]->_processBlock($block, CRYPT_DES_DECRYPT); + $plaintext.= $block ^ $xor; + $xor = $orig; + } + if ($this->continuousBuffer) { + $this->decryptIV = $xor; + } + } + + return $this->_unpad($plaintext); + } + + /** + * Treat consecutive "packets" as if they are a continuous buffer. + * + * Say you have a 16-byte plaintext $plaintext. Using the default behavior, the two following code snippets + * will yield different outputs: + * + * + * echo $des->encrypt(substr($plaintext, 0, 8)); + * echo $des->encrypt(substr($plaintext, 8, 8)); + * + * + * echo $des->encrypt($plaintext); + * + * + * The solution is to enable the continuous buffer. Although this will resolve the above discrepancy, it creates + * another, as demonstrated with the following: + * + * + * $des->encrypt(substr($plaintext, 0, 8)); + * echo $des->decrypt($des->encrypt(substr($plaintext, 8, 8))); + * + * + * echo $des->decrypt($des->encrypt(substr($plaintext, 8, 8))); + * + * + * With the continuous buffer disabled, these would yield the same output. With it enabled, they yield different + * outputs. The reason is due to the fact that the initialization vector's change after every encryption / + * decryption round when the continuous buffer is enabled. When it's disabled, they remain constant. + * + * Put another way, when the continuous buffer is enabled, the state of the Crypt_DES() object changes after each + * encryption / decryption round, whereas otherwise, it'd remain constant. For this reason, it's recommended that + * continuous buffers not be used. They do offer better security and are, in fact, sometimes required (SSH uses them), + * however, they are also less intuitive and more likely to cause you problems. + * + * @see Crypt_TripleDES::disableContinuousBuffer() + * @access public + */ + function enableContinuousBuffer() + { + $this->continuousBuffer = true; + if ($this->mode == CRYPT_DES_MODE_3CBC) { + $this->des[0]->enableContinuousBuffer(); + $this->des[1]->enableContinuousBuffer(); + $this->des[2]->enableContinuousBuffer(); + } + } + + /** + * Treat consecutive packets as if they are a discontinuous buffer. + * + * The default behavior. + * + * @see Crypt_TripleDES::enableContinuousBuffer() + * @access public + */ + function disableContinuousBuffer() + { + $this->continuousBuffer = false; + $this->encryptIV = $this->iv; + $this->decryptIV = $this->iv; + + if ($this->mode == CRYPT_DES_MODE_3CBC) { + $this->des[0]->disableContinuousBuffer(); + $this->des[1]->disableContinuousBuffer(); + $this->des[2]->disableContinuousBuffer(); + } + } + + /** + * Pad "packets". + * + * DES works by encrypting eight bytes at a time. If you ever need to encrypt or decrypt something that's not + * a multiple of eight, it becomes necessary to pad the input so that it's length is a multiple of eight. + * + * Padding is enabled by default. Sometimes, however, it is undesirable to pad strings. Such is the case in SSH1, + * where "packets" are padded with random bytes before being encrypted. Unpad these packets and you risk stripping + * away characters that shouldn't be stripped away. (SSH knows how many bytes are added because the length is + * transmitted separately) + * + * @see Crypt_TripleDES::disablePadding() + * @access public + */ + function enablePadding() + { + $this->padding = true; + } + + /** + * Do not pad packets. + * + * @see Crypt_TripleDES::enablePadding() + * @access public + */ + function disablePadding() + { + $this->padding = false; + } + + /** + * Pads a string + * + * Pads a string using the RSA PKCS padding standards so that its length is a multiple of the blocksize (8). + * 8 - (strlen($text) & 7) bytes are added, each of which is equal to chr(8 - (strlen($text) & 7) + * + * If padding is disabled and $text is not a multiple of the blocksize, the string will be padded regardless + * and padding will, hence forth, be enabled. + * + * @see Crypt_TripleDES::_unpad() + * @access private + */ + function _pad($text) + { + $length = strlen($text); + + if (!$this->padding) { + if (($length & 7) == 0) { + return $text; + } else { + user_error("The plaintext's length ($length) is not a multiple of the block size (8)", E_USER_NOTICE); + $this->padding = true; + } + } + + $pad = 8 - ($length & 7); + return str_pad($text, $length + $pad, chr($pad)); + } + + /** + * Unpads a string + * + * If padding is enabled and the reported padding length is invalid, padding will be, hence forth, disabled. + * + * @see Crypt_TripleDES::_pad() + * @access private + */ + function _unpad($text) + { + if (!$this->padding) { + return $text; + } + + $length = ord($text[strlen($text) - 1]); + + if (!$length || $length > 8) { + user_error("The number of bytes reported as being padded ($length) is invalid (block size = 8)", E_USER_NOTICE); + $this->padding = false; + return $text; + } + + return substr($text, 0, -$length); + } +} + +// vim: ts=4:sw=4:et: +// vim6: fdl=1: \ No newline at end of file diff --git a/plugins/OStatus/extlib/Math/BigInteger.php b/plugins/OStatus/extlib/Math/BigInteger.php new file mode 100644 index 000000000..ce0e08354 --- /dev/null +++ b/plugins/OStatus/extlib/Math/BigInteger.php @@ -0,0 +1,3060 @@ +> and << cannot be used, nor can the modulo operator %, + * which only supports integers. Although this fact will slow this library down, the fact that such a high + * base is being used should more than compensate. + * + * When PHP version 6 is officially released, we'll be able to use 64-bit integers. This should, once again, + * allow bitwise operators, and will increase the maximum possible base to 2**31 (or 2**62 for addition / + * subtraction). + * + * Useful resources are as follows: + * + * - {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf Handbook of Applied Cryptography (HAC)} + * - {@link http://math.libtomcrypt.com/files/tommath.pdf Multi-Precision Math (MPM)} + * - Java's BigInteger classes. See /j2se/src/share/classes/java/math in jdk-1_5_0-src-jrl.zip + * + * One idea for optimization is to use the comba method to reduce the number of operations performed. + * MPM uses this quite extensively. The following URL elaborates: + * + * {@link http://www.everything2.com/index.pl?node_id=1736418}}} + * + * Here's an example of how to use this library: + * + * add($b); + * + * echo $c->toString(); // outputs 5 + * ?> + * + * + * LICENSE: This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * @category Math + * @package Math_BigInteger + * @author Jim Wigginton + * @copyright MMVI Jim Wigginton + * @license http://www.gnu.org/licenses/lgpl.txt + * @version $Id: BigInteger.php,v 1.18 2009/12/04 19:12:18 terrafrost Exp $ + * @link http://pear.php.net/package/Math_BigInteger + */ + +/**#@+ + * @access private + * @see Math_BigInteger::_slidingWindow() + */ +/** + * @see Math_BigInteger::_montgomery() + * @see Math_BigInteger::_prepMontgomery() + */ +define('MATH_BIGINTEGER_MONTGOMERY', 0); +/** + * @see Math_BigInteger::_barrett() + */ +define('MATH_BIGINTEGER_BARRETT', 1); +/** + * @see Math_BigInteger::_mod2() + */ +define('MATH_BIGINTEGER_POWEROF2', 2); +/** + * @see Math_BigInteger::_remainder() + */ +define('MATH_BIGINTEGER_CLASSIC', 3); +/** + * @see Math_BigInteger::__clone() + */ +define('MATH_BIGINTEGER_NONE', 4); +/**#@-*/ + +/**#@+ + * @access private + * @see Math_BigInteger::_montgomery() + * @see Math_BigInteger::_barrett() + */ +/** + * $cache[MATH_BIGINTEGER_VARIABLE] tells us whether or not the cached data is still valid. + */ +define('MATH_BIGINTEGER_VARIABLE', 0); +/** + * $cache[MATH_BIGINTEGER_DATA] contains the cached data. + */ +define('MATH_BIGINTEGER_DATA', 1); +/**#@-*/ + +/**#@+ + * @access private + * @see Math_BigInteger::Math_BigInteger() + */ +/** + * To use the pure-PHP implementation + */ +define('MATH_BIGINTEGER_MODE_INTERNAL', 1); +/** + * To use the BCMath library + * + * (if enabled; otherwise, the internal implementation will be used) + */ +define('MATH_BIGINTEGER_MODE_BCMATH', 2); +/** + * To use the GMP library + * + * (if present; otherwise, either the BCMath or the internal implementation will be used) + */ +define('MATH_BIGINTEGER_MODE_GMP', 3); +/**#@-*/ + +/** + * The largest digit that may be used in addition / subtraction + * + * (we do pow(2, 52) instead of using 4503599627370496, directly, because some PHP installations + * will truncate 4503599627370496) + * + * @access private + */ +define('MATH_BIGINTEGER_MAX_DIGIT52', pow(2, 52)); + +/** + * Karatsuba Cutoff + * + * At what point do we switch between Karatsuba multiplication and schoolbook long multiplication? + * + * @access private + */ +define('MATH_BIGINTEGER_KARATSUBA_CUTOFF', 15); + +/** + * Pure-PHP arbitrary precision integer arithmetic library. Supports base-2, base-10, base-16, and base-256 + * numbers. + * + * @author Jim Wigginton + * @version 1.0.0RC3 + * @access public + * @package Math_BigInteger + */ +class Math_BigInteger { + /** + * Holds the BigInteger's value. + * + * @var Array + * @access private + */ + var $value; + + /** + * Holds the BigInteger's magnitude. + * + * @var Boolean + * @access private + */ + var $is_negative = false; + + /** + * Random number generator function + * + * @see setRandomGenerator() + * @access private + */ + var $generator = 'mt_rand'; + + /** + * Precision + * + * @see setPrecision() + * @access private + */ + var $precision = -1; + + /** + * Precision Bitmask + * + * @see setPrecision() + * @access private + */ + var $bitmask = false; + + /** + * Converts base-2, base-10, base-16, and binary strings (eg. base-256) to BigIntegers. + * + * If the second parameter - $base - is negative, then it will be assumed that the number's are encoded using + * two's compliment. The sole exception to this is -10, which is treated the same as 10 is. + * + * Here's an example: + * + * toString(); // outputs 50 + * ?> + * + * + * @param optional $x base-10 number or base-$base number if $base set. + * @param optional integer $base + * @return Math_BigInteger + * @access public + */ + function Math_BigInteger($x = 0, $base = 10) + { + if ( !defined('MATH_BIGINTEGER_MODE') ) { + switch (true) { + case extension_loaded('gmp'): + define('MATH_BIGINTEGER_MODE', MATH_BIGINTEGER_MODE_GMP); + break; + case extension_loaded('bcmath'): + define('MATH_BIGINTEGER_MODE', MATH_BIGINTEGER_MODE_BCMATH); + break; + default: + define('MATH_BIGINTEGER_MODE', MATH_BIGINTEGER_MODE_INTERNAL); + } + } + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + if (is_resource($x) && get_resource_type($x) == 'GMP integer') { + $this->value = $x; + return; + } + $this->value = gmp_init(0); + break; + case MATH_BIGINTEGER_MODE_BCMATH: + $this->value = '0'; + break; + default: + $this->value = array(); + } + + if ($x === 0) { + return; + } + + switch ($base) { + case -256: + if (ord($x[0]) & 0x80) { + $x = ~$x; + $this->is_negative = true; + } + case 256: + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $sign = $this->is_negative ? '-' : ''; + $this->value = gmp_init($sign . '0x' . bin2hex($x)); + break; + case MATH_BIGINTEGER_MODE_BCMATH: + // round $len to the nearest 4 (thanks, DavidMJ!) + $len = (strlen($x) + 3) & 0xFFFFFFFC; + + $x = str_pad($x, $len, chr(0), STR_PAD_LEFT); + + for ($i = 0; $i < $len; $i+= 4) { + $this->value = bcmul($this->value, '4294967296'); // 4294967296 == 2**32 + $this->value = bcadd($this->value, 0x1000000 * ord($x[$i]) + ((ord($x[$i + 1]) << 16) | (ord($x[$i + 2]) << 8) | ord($x[$i + 3]))); + } + + if ($this->is_negative) { + $this->value = '-' . $this->value; + } + + break; + // converts a base-2**8 (big endian / msb) number to base-2**26 (little endian / lsb) + default: + while (strlen($x)) { + $this->value[] = $this->_bytes2int($this->_base256_rshift($x, 26)); + } + } + + if ($this->is_negative) { + if (MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_INTERNAL) { + $this->is_negative = false; + } + $temp = $this->add(new Math_BigInteger('-1')); + $this->value = $temp->value; + } + break; + case 16: + case -16: + if ($base > 0 && $x[0] == '-') { + $this->is_negative = true; + $x = substr($x, 1); + } + + $x = preg_replace('#^(?:0x)?([A-Fa-f0-9]*).*#', '$1', $x); + + $is_negative = false; + if ($base < 0 && hexdec($x[0]) >= 8) { + $this->is_negative = $is_negative = true; + $x = bin2hex(~pack('H*', $x)); + } + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = $this->is_negative ? '-0x' . $x : '0x' . $x; + $this->value = gmp_init($temp); + $this->is_negative = false; + break; + case MATH_BIGINTEGER_MODE_BCMATH: + $x = ( strlen($x) & 1 ) ? '0' . $x : $x; + $temp = new Math_BigInteger(pack('H*', $x), 256); + $this->value = $this->is_negative ? '-' . $temp->value : $temp->value; + $this->is_negative = false; + break; + default: + $x = ( strlen($x) & 1 ) ? '0' . $x : $x; + $temp = new Math_BigInteger(pack('H*', $x), 256); + $this->value = $temp->value; + } + + if ($is_negative) { + $temp = $this->add(new Math_BigInteger('-1')); + $this->value = $temp->value; + } + break; + case 10: + case -10: + $x = preg_replace('#^(-?[0-9]*).*#', '$1', $x); + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $this->value = gmp_init($x); + break; + case MATH_BIGINTEGER_MODE_BCMATH: + // explicitly casting $x to a string is necessary, here, since doing $x[0] on -1 yields different + // results then doing it on '-1' does (modInverse does $x[0]) + $this->value = (string) $x; + break; + default: + $temp = new Math_BigInteger(); + + // array(10000000) is 10**7 in base-2**26. 10**7 is the closest to 2**26 we can get without passing it. + $multiplier = new Math_BigInteger(); + $multiplier->value = array(10000000); + + if ($x[0] == '-') { + $this->is_negative = true; + $x = substr($x, 1); + } + + $x = str_pad($x, strlen($x) + (6 * strlen($x)) % 7, 0, STR_PAD_LEFT); + + while (strlen($x)) { + $temp = $temp->multiply($multiplier); + $temp = $temp->add(new Math_BigInteger($this->_int2bytes(substr($x, 0, 7)), 256)); + $x = substr($x, 7); + } + + $this->value = $temp->value; + } + break; + case 2: // base-2 support originally implemented by Lluis Pamies - thanks! + case -2: + if ($base > 0 && $x[0] == '-') { + $this->is_negative = true; + $x = substr($x, 1); + } + + $x = preg_replace('#^([01]*).*#', '$1', $x); + $x = str_pad($x, strlen($x) + (3 * strlen($x)) % 4, 0, STR_PAD_LEFT); + + $str = '0x'; + while (strlen($x)) { + $part = substr($x, 0, 4); + $str.= dechex(bindec($part)); + $x = substr($x, 4); + } + + if ($this->is_negative) { + $str = '-' . $str; + } + + $temp = new Math_BigInteger($str, 8 * $base); // ie. either -16 or +16 + $this->value = $temp->value; + $this->is_negative = $temp->is_negative; + + break; + default: + // base not supported, so we'll let $this == 0 + } + } + + /** + * Converts a BigInteger to a byte string (eg. base-256). + * + * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're + * saved as two's compliment. + * + * Here's an example: + * + * toBytes(); // outputs chr(65) + * ?> + * + * + * @param Boolean $twos_compliment + * @return String + * @access public + * @internal Converts a base-2**26 number to base-2**8 + */ + function toBytes($twos_compliment = false) + { + if ($twos_compliment) { + $comparison = $this->compare(new Math_BigInteger()); + if ($comparison == 0) { + return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; + } + + $temp = $comparison < 0 ? $this->add(new Math_BigInteger(1)) : $this->copy(); + $bytes = $temp->toBytes(); + + if (empty($bytes)) { // eg. if the number we're trying to convert is -1 + $bytes = chr(0); + } + + if (ord($bytes[0]) & 0x80) { + $bytes = chr(0) . $bytes; + } + + return $comparison < 0 ? ~$bytes : $bytes; + } + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + if (gmp_cmp($this->value, gmp_init(0)) == 0) { + return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; + } + + $temp = gmp_strval(gmp_abs($this->value), 16); + $temp = ( strlen($temp) & 1 ) ? '0' . $temp : $temp; + $temp = pack('H*', $temp); + + return $this->precision > 0 ? + substr(str_pad($temp, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) : + ltrim($temp, chr(0)); + case MATH_BIGINTEGER_MODE_BCMATH: + if ($this->value === '0') { + return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; + } + + $value = ''; + $current = $this->value; + + if ($current[0] == '-') { + $current = substr($current, 1); + } + + // we don't do four bytes at a time because then numbers larger than 1<<31 would be negative + // two's complimented numbers, which would break chr. + while (bccomp($current, '0') > 0) { + $temp = bcmod($current, 0x1000000); + $value = chr($temp >> 16) . chr($temp >> 8) . chr($temp) . $value; + $current = bcdiv($current, 0x1000000); + } + + return $this->precision > 0 ? + substr(str_pad($value, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) : + ltrim($value, chr(0)); + } + + if (!count($this->value)) { + return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; + } + $result = $this->_int2bytes($this->value[count($this->value) - 1]); + + $temp = $this->copy(); + + for ($i = count($temp->value) - 2; $i >= 0; $i--) { + $temp->_base256_lshift($result, 26); + $result = $result | str_pad($temp->_int2bytes($temp->value[$i]), strlen($result), chr(0), STR_PAD_LEFT); + } + + return $this->precision > 0 ? + substr(str_pad($result, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) : + $result; + } + + /** + * Converts a BigInteger to a hex string (eg. base-16)). + * + * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're + * saved as two's compliment. + * + * Here's an example: + * + * toHex(); // outputs '41' + * ?> + * + * + * @param Boolean $twos_compliment + * @return String + * @access public + * @internal Converts a base-2**26 number to base-2**8 + */ + function toHex($twos_compliment = false) + { + return bin2hex($this->toBytes($twos_compliment)); + } + + /** + * Converts a BigInteger to a bit string (eg. base-2). + * + * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're + * saved as two's compliment. + * + * Here's an example: + * + * toBits(); // outputs '1000001' + * ?> + * + * + * @param Boolean $twos_compliment + * @return String + * @access public + * @internal Converts a base-2**26 number to base-2**2 + */ + function toBits($twos_compliment = false) + { + $hex = $this->toHex($twos_compliment); + $bits = ''; + for ($i = 0; $i < strlen($hex); $i+=8) { + $bits.= str_pad(decbin(hexdec(substr($hex, $i, 8))), 32, '0', STR_PAD_LEFT); + } + return $this->precision > 0 ? substr($bits, -$this->precision) : ltrim($bits, '0'); + } + + /** + * Converts a BigInteger to a base-10 number. + * + * Here's an example: + * + * toString(); // outputs 50 + * ?> + * + * + * @return String + * @access public + * @internal Converts a base-2**26 number to base-10**7 (which is pretty much base-10) + */ + function toString() + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + return gmp_strval($this->value); + case MATH_BIGINTEGER_MODE_BCMATH: + if ($this->value === '0') { + return '0'; + } + + return ltrim($this->value, '0'); + } + + if (!count($this->value)) { + return '0'; + } + + $temp = $this->copy(); + $temp->is_negative = false; + + $divisor = new Math_BigInteger(); + $divisor->value = array(10000000); // eg. 10**7 + $result = ''; + while (count($temp->value)) { + list($temp, $mod) = $temp->divide($divisor); + $result = str_pad($mod->value[0], 7, '0', STR_PAD_LEFT) . $result; + } + $result = ltrim($result, '0'); + + if ($this->is_negative) { + $result = '-' . $result; + } + + return $result; + } + + /** + * Copy an object + * + * PHP5 passes objects by reference while PHP4 passes by value. As such, we need a function to guarantee + * that all objects are passed by value, when appropriate. More information can be found here: + * + * {@link http://php.net/language.oop5.basic#51624} + * + * @access public + * @see __clone() + * @return Math_BigInteger + */ + function copy() + { + $temp = new Math_BigInteger(); + $temp->value = $this->value; + $temp->is_negative = $this->is_negative; + $temp->generator = $this->generator; + $temp->precision = $this->precision; + $temp->bitmask = $this->bitmask; + return $temp; + } + + /** + * __toString() magic method + * + * Will be called, automatically, if you're supporting just PHP5. If you're supporting PHP4, you'll need to call + * toString(). + * + * @access public + * @internal Implemented per a suggestion by Techie-Michael - thanks! + */ + function __toString() + { + return $this->toString(); + } + + /** + * __clone() magic method + * + * Although you can call Math_BigInteger::__toString() directly in PHP5, you cannot call Math_BigInteger::__clone() + * directly in PHP5. You can in PHP4 since it's not a magic method, but in PHP5, you have to call it by using the PHP5 + * only syntax of $y = clone $x. As such, if you're trying to write an application that works on both PHP4 and PHP5, + * call Math_BigInteger::copy(), instead. + * + * @access public + * @see copy() + * @return Math_BigInteger + */ + function __clone() + { + return $this->copy(); + } + + /** + * Adds two BigIntegers. + * + * Here's an example: + * + * add($b); + * + * echo $c->toString(); // outputs 30 + * ?> + * + * + * @param Math_BigInteger $y + * @return Math_BigInteger + * @access public + * @internal Performs base-2**52 addition + */ + function add($y) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_add($this->value, $y->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $temp = new Math_BigInteger(); + $temp->value = bcadd($this->value, $y->value); + + return $this->_normalize($temp); + } + + $this_size = count($this->value); + $y_size = count($y->value); + + if ($this_size == 0) { + return $y->copy(); + } else if ($y_size == 0) { + return $this->copy(); + } + + // subtract, if appropriate + if ( $this->is_negative != $y->is_negative ) { + // is $y the negative number? + $y_negative = $this->compare($y) > 0; + + $temp = $this->copy(); + $y = $y->copy(); + $temp->is_negative = $y->is_negative = false; + + $diff = $temp->compare($y); + if ( !$diff ) { + $temp = new Math_BigInteger(); + return $this->_normalize($temp); + } + + $temp = $temp->subtract($y); + + $temp->is_negative = ($diff > 0) ? !$y_negative : $y_negative; + + return $this->_normalize($temp); + } + + $result = new Math_BigInteger(); + $carry = 0; + + $size = max($this_size, $y_size); + $size+= $size & 1; // rounds $size to the nearest 2. + + $x = array_pad($this->value, $size, 0); + $y = array_pad($y->value, $size, 0); + + for ($i = 0; $i < $size - 1; $i+=2) { + $sum = $x[$i + 1] * 0x4000000 + $x[$i] + $y[$i + 1] * 0x4000000 + $y[$i] + $carry; + $carry = $sum >= MATH_BIGINTEGER_MAX_DIGIT52; // eg. floor($sum / 2**52); only possible values (in any base) are 0 and 1 + $sum = $carry ? $sum - MATH_BIGINTEGER_MAX_DIGIT52 : $sum; + + $temp = floor($sum / 0x4000000); + + $result->value[] = $sum - 0x4000000 * $temp; // eg. a faster alternative to fmod($sum, 0x4000000) + $result->value[] = $temp; + } + + if ($carry) { + $result->value[] = (int) $carry; + } + + $result->is_negative = $this->is_negative; + + return $this->_normalize($result); + } + + /** + * Subtracts two BigIntegers. + * + * Here's an example: + * + * subtract($b); + * + * echo $c->toString(); // outputs -10 + * ?> + * + * + * @param Math_BigInteger $y + * @return Math_BigInteger + * @access public + * @internal Performs base-2**52 subtraction + */ + function subtract($y) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_sub($this->value, $y->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $temp = new Math_BigInteger(); + $temp->value = bcsub($this->value, $y->value); + + return $this->_normalize($temp); + } + + $this_size = count($this->value); + $y_size = count($y->value); + + if ($this_size == 0) { + $temp = $y->copy(); + $temp->is_negative = !$temp->is_negative; + return $temp; + } else if ($y_size == 0) { + return $this->copy(); + } + + // add, if appropriate (ie. -$x - +$y or +$x - -$y) + if ( $this->is_negative != $y->is_negative ) { + $is_negative = $y->compare($this) > 0; + + $temp = $this->copy(); + $y = $y->copy(); + $temp->is_negative = $y->is_negative = false; + + $temp = $temp->add($y); + + $temp->is_negative = $is_negative; + + return $this->_normalize($temp); + } + + $diff = $this->compare($y); + + if ( !$diff ) { + $temp = new Math_BigInteger(); + return $this->_normalize($temp); + } + + // switch $this and $y around, if appropriate. + if ( (!$this->is_negative && $diff < 0) || ($this->is_negative && $diff > 0) ) { + $is_negative = $y->is_negative; + + $temp = $this->copy(); + $y = $y->copy(); + $temp->is_negative = $y->is_negative = false; + + $temp = $y->subtract($temp); + $temp->is_negative = !$is_negative; + + return $this->_normalize($temp); + } + + $result = new Math_BigInteger(); + $carry = 0; + + $size = max($this_size, $y_size); + $size+= $size % 2; + + $x = array_pad($this->value, $size, 0); + $y = array_pad($y->value, $size, 0); + + for ($i = 0; $i < $size - 1; $i+=2) { + $sum = $x[$i + 1] * 0x4000000 + $x[$i] - $y[$i + 1] * 0x4000000 - $y[$i] + $carry; + $carry = $sum < 0 ? -1 : 0; // eg. floor($sum / 2**52); only possible values (in any base) are 0 and 1 + $sum = $carry ? $sum + MATH_BIGINTEGER_MAX_DIGIT52 : $sum; + + $temp = floor($sum / 0x4000000); + + $result->value[] = $sum - 0x4000000 * $temp; + $result->value[] = $temp; + } + + // $carry shouldn't be anything other than zero, at this point, since we already made sure that $this + // was bigger than $y. + + $result->is_negative = $this->is_negative; + + return $this->_normalize($result); + } + + /** + * Multiplies two BigIntegers + * + * Here's an example: + * + * multiply($b); + * + * echo $c->toString(); // outputs 200 + * ?> + * + * + * @param Math_BigInteger $x + * @return Math_BigInteger + * @access public + */ + function multiply($x) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_mul($this->value, $x->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $temp = new Math_BigInteger(); + $temp->value = bcmul($this->value, $x->value); + + return $this->_normalize($temp); + } + + static $cutoff = false; + if ($cutoff === false) { + $cutoff = 2 * MATH_BIGINTEGER_KARATSUBA_CUTOFF; + } + + if ( $this->equals($x) ) { + return $this->_square(); + } + + $this_length = count($this->value); + $x_length = count($x->value); + + if ( !$this_length || !$x_length ) { // a 0 is being multiplied + $temp = new Math_BigInteger(); + return $this->_normalize($temp); + } + + $product = min($this_length, $x_length) < $cutoff ? $this->_multiply($x) : $this->_karatsuba($x); + + $product->is_negative = $this->is_negative != $x->is_negative; + + return $this->_normalize($product); + } + + /** + * Performs long multiplication up to $stop digits + * + * If you're going to be doing array_slice($product->value, 0, $stop), some cycles can be saved. + * + * @see _barrett() + * @param Math_BigInteger $x + * @return Math_BigInteger + * @access private + */ + function _multiplyLower($x, $stop) + { + $this_length = count($this->value); + $x_length = count($x->value); + + if ( !$this_length || !$x_length ) { // a 0 is being multiplied + return new Math_BigInteger(); + } + + if ( $this_length < $x_length ) { + return $x->_multiplyLower($this, $stop); + } + + $product = new Math_BigInteger(); + $product->value = $this->_array_repeat(0, $this_length + $x_length); + + // the following for loop could be removed if the for loop following it + // (the one with nested for loops) initially set $i to 0, but + // doing so would also make the result in one set of unnecessary adds, + // since on the outermost loops first pass, $product->value[$k] is going + // to always be 0 + + $carry = 0; + + for ($j = 0; $j < $this_length; $j++) { // ie. $i = 0, $k = $i + $temp = $this->value[$j] * $x->value[0] + $carry; // $product->value[$k] == 0 + $carry = floor($temp / 0x4000000); + $product->value[$j] = $temp - 0x4000000 * $carry; + } + + if ($j < $stop) { + $product->value[$j] = $carry; + } + + // the above for loop is what the previous comment was talking about. the + // following for loop is the "one with nested for loops" + + for ($i = 1; $i < $x_length; $i++) { + $carry = 0; + + for ($j = 0, $k = $i; $j < $this_length && $k < $stop; $j++, $k++) { + $temp = $product->value[$k] + $this->value[$j] * $x->value[$i] + $carry; + $carry = floor($temp / 0x4000000); + $product->value[$k] = $temp - 0x4000000 * $carry; + } + + if ($k < $stop) { + $product->value[$k] = $carry; + } + } + + $product->is_negative = $this->is_negative != $x->is_negative; + + return $product; + } + + /** + * Performs long multiplication on two BigIntegers + * + * Modeled after 'multiply' in MutableBigInteger.java. + * + * @param Math_BigInteger $x + * @return Math_BigInteger + * @access private + */ + function _multiply($x) + { + $this_length = count($this->value); + $x_length = count($x->value); + + if ( !$this_length || !$x_length ) { // a 0 is being multiplied + return new Math_BigInteger(); + } + + if ( $this_length < $x_length ) { + return $x->_multiply($this); + } + + $product = new Math_BigInteger(); + $product->value = $this->_array_repeat(0, $this_length + $x_length); + + // the following for loop could be removed if the for loop following it + // (the one with nested for loops) initially set $i to 0, but + // doing so would also make the result in one set of unnecessary adds, + // since on the outermost loops first pass, $product->value[$k] is going + // to always be 0 + + $carry = 0; + + for ($j = 0; $j < $this_length; $j++) { // ie. $i = 0 + $temp = $this->value[$j] * $x->value[0] + $carry; // $product->value[$k] == 0 + $carry = floor($temp / 0x4000000); + $product->value[$j] = $temp - 0x4000000 * $carry; + } + + $product->value[$j] = $carry; + + // the above for loop is what the previous comment was talking about. the + // following for loop is the "one with nested for loops" + for ($i = 1; $i < $x_length; $i++) { + $carry = 0; + + for ($j = 0, $k = $i; $j < $this_length; $j++, $k++) { + $temp = $product->value[$k] + $this->value[$j] * $x->value[$i] + $carry; + $carry = floor($temp / 0x4000000); + $product->value[$k] = $temp - 0x4000000 * $carry; + } + + $product->value[$k] = $carry; + } + + $product->is_negative = $this->is_negative != $x->is_negative; + + return $this->_normalize($product); + } + + /** + * Performs Karatsuba multiplication on two BigIntegers + * + * See {@link http://en.wikipedia.org/wiki/Karatsuba_algorithm Karatsuba algorithm} and + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=120 MPM 5.2.3}. + * + * @param Math_BigInteger $y + * @return Math_BigInteger + * @access private + */ + function _karatsuba($y) + { + $x = $this->copy(); + + $m = min(count($x->value) >> 1, count($y->value) >> 1); + + if ($m < MATH_BIGINTEGER_KARATSUBA_CUTOFF) { + return $x->_multiply($y); + } + + $x1 = new Math_BigInteger(); + $x0 = new Math_BigInteger(); + $y1 = new Math_BigInteger(); + $y0 = new Math_BigInteger(); + + $x1->value = array_slice($x->value, $m); + $x0->value = array_slice($x->value, 0, $m); + $y1->value = array_slice($y->value, $m); + $y0->value = array_slice($y->value, 0, $m); + + $z2 = $x1->_karatsuba($y1); + $z0 = $x0->_karatsuba($y0); + + $z1 = $x1->add($x0); + $z1 = $z1->_karatsuba($y1->add($y0)); + $z1 = $z1->subtract($z2->add($z0)); + + $z2->value = array_merge(array_fill(0, 2 * $m, 0), $z2->value); + $z1->value = array_merge(array_fill(0, $m, 0), $z1->value); + + $xy = $z2->add($z1); + $xy = $xy->add($z0); + + return $xy; + } + + /** + * Squares a BigInteger + * + * @return Math_BigInteger + * @access private + */ + function _square() + { + static $cutoff = false; + if ($cutoff === false) { + $cutoff = 2 * MATH_BIGINTEGER_KARATSUBA_CUTOFF; + } + + return count($this->value) < $cutoff ? $this->_baseSquare() : $this->_karatsubaSquare(); + } + + /** + * Performs traditional squaring on two BigIntegers + * + * Squaring can be done faster than multiplying a number by itself can be. See + * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=7 HAC 14.2.4} / + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=141 MPM 5.3} for more information. + * + * @return Math_BigInteger + * @access private + */ + function _baseSquare() + { + if ( empty($this->value) ) { + return new Math_BigInteger(); + } + + $square = new Math_BigInteger(); + $square->value = $this->_array_repeat(0, 2 * count($this->value)); + + for ($i = 0, $max_index = count($this->value) - 1; $i <= $max_index; $i++) { + $i2 = 2 * $i; + + $temp = $square->value[$i2] + $this->value[$i] * $this->value[$i]; + $carry = floor($temp / 0x4000000); + $square->value[$i2] = $temp - 0x4000000 * $carry; + + // note how we start from $i+1 instead of 0 as we do in multiplication. + for ($j = $i + 1, $k = $i2 + 1; $j <= $max_index; $j++, $k++) { + $temp = $square->value[$k] + 2 * $this->value[$j] * $this->value[$i] + $carry; + $carry = floor($temp / 0x4000000); + $square->value[$k] = $temp - 0x4000000 * $carry; + } + + // the following line can yield values larger 2**15. at this point, PHP should switch + // over to floats. + $square->value[$i + $max_index + 1] = $carry; + } + + return $square; + } + + /** + * Performs Karatsuba "squaring" on two BigIntegers + * + * See {@link http://en.wikipedia.org/wiki/Karatsuba_algorithm Karatsuba algorithm} and + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=151 MPM 5.3.4}. + * + * @param Math_BigInteger $y + * @return Math_BigInteger + * @access private + */ + function _karatsubaSquare() + { + $m = count($this->value) >> 1; + + if ($m < MATH_BIGINTEGER_KARATSUBA_CUTOFF) { + return $this->_square(); + } + + $x1 = new Math_BigInteger(); + $x0 = new Math_BigInteger(); + + $x1->value = array_slice($this->value, $m); + $x0->value = array_slice($this->value, 0, $m); + + $z2 = $x1->_karatsubaSquare(); + $z0 = $x0->_karatsubaSquare(); + + $z1 = $x1->add($x0); + $z1 = $z1->_karatsubaSquare(); + $z1 = $z1->subtract($z2->add($z0)); + + $z2->value = array_merge(array_fill(0, 2 * $m, 0), $z2->value); + $z1->value = array_merge(array_fill(0, $m, 0), $z1->value); + + $xx = $z2->add($z1); + $xx = $xx->add($z0); + + return $xx; + } + + /** + * Divides two BigIntegers. + * + * Returns an array whose first element contains the quotient and whose second element contains the + * "common residue". If the remainder would be positive, the "common residue" and the remainder are the + * same. If the remainder would be negative, the "common residue" is equal to the sum of the remainder + * and the divisor (basically, the "common residue" is the first positive modulo). + * + * Here's an example: + * + * divide($b); + * + * echo $quotient->toString(); // outputs 0 + * echo "\r\n"; + * echo $remainder->toString(); // outputs 10 + * ?> + * + * + * @param Math_BigInteger $y + * @return Array + * @access public + * @internal This function is based off of {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=9 HAC 14.20}. + */ + function divide($y) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $quotient = new Math_BigInteger(); + $remainder = new Math_BigInteger(); + + list($quotient->value, $remainder->value) = gmp_div_qr($this->value, $y->value); + + if (gmp_sign($remainder->value) < 0) { + $remainder->value = gmp_add($remainder->value, gmp_abs($y->value)); + } + + return array($this->_normalize($quotient), $this->_normalize($remainder)); + case MATH_BIGINTEGER_MODE_BCMATH: + $quotient = new Math_BigInteger(); + $remainder = new Math_BigInteger(); + + $quotient->value = bcdiv($this->value, $y->value); + $remainder->value = bcmod($this->value, $y->value); + + if ($remainder->value[0] == '-') { + $remainder->value = bcadd($remainder->value, $y->value[0] == '-' ? substr($y->value, 1) : $y->value); + } + + return array($this->_normalize($quotient), $this->_normalize($remainder)); + } + + if (count($y->value) == 1) { + $temp = $this->_divide_digit($y->value[0]); + $temp[0]->is_negative = $this->is_negative != $y->is_negative; + return array($this->_normalize($temp[0]), $this->_normalize($temp[1])); + } + + static $zero; + if (!isset($zero)) { + $zero = new Math_BigInteger(); + } + + $x = $this->copy(); + $y = $y->copy(); + + $x_sign = $x->is_negative; + $y_sign = $y->is_negative; + + $x->is_negative = $y->is_negative = false; + + $diff = $x->compare($y); + + if ( !$diff ) { + $temp = new Math_BigInteger(); + $temp->value = array(1); + $temp->is_negative = $x_sign != $y_sign; + return array($this->_normalize($temp), $this->_normalize(new Math_BigInteger())); + } + + if ( $diff < 0 ) { + // if $x is negative, "add" $y. + if ( $x_sign ) { + $x = $y->subtract($x); + } + return array($this->_normalize(new Math_BigInteger()), $this->_normalize($x)); + } + + // normalize $x and $y as described in HAC 14.23 / 14.24 + $msb = $y->value[count($y->value) - 1]; + for ($shift = 0; !($msb & 0x2000000); $shift++) { + $msb <<= 1; + } + $x->_lshift($shift); + $y->_lshift($shift); + + $x_max = count($x->value) - 1; + $y_max = count($y->value) - 1; + + $quotient = new Math_BigInteger(); + $quotient->value = $this->_array_repeat(0, $x_max - $y_max + 1); + + // $temp = $y << ($x_max - $y_max-1) in base 2**26 + $temp = new Math_BigInteger(); + $temp->value = array_merge($this->_array_repeat(0, $x_max - $y_max), $y->value); + + while ( $x->compare($temp) >= 0 ) { + // calculate the "common residue" + $quotient->value[$x_max - $y_max]++; + $x = $x->subtract($temp); + $x_max = count($x->value) - 1; + } + + for ($i = $x_max; $i >= $y_max + 1; $i--) { + $x_value = array( + $x->value[$i], + ( $i > 0 ) ? $x->value[$i - 1] : 0, + ( $i > 1 ) ? $x->value[$i - 2] : 0 + ); + $y_value = array( + $y->value[$y_max], + ( $y_max > 0 ) ? $y->value[$y_max - 1] : 0 + ); + + $q_index = $i - $y_max - 1; + if ($x_value[0] == $y_value[0]) { + $quotient->value[$q_index] = 0x3FFFFFF; + } else { + $quotient->value[$q_index] = floor( + ($x_value[0] * 0x4000000 + $x_value[1]) + / + $y_value[0] + ); + } + + $temp = new Math_BigInteger(); + $temp->value = array($y_value[1], $y_value[0]); + + $lhs = new Math_BigInteger(); + $lhs->value = array($quotient->value[$q_index]); + $lhs = $lhs->multiply($temp); + + $rhs = new Math_BigInteger(); + $rhs->value = array($x_value[2], $x_value[1], $x_value[0]); + + while ( $lhs->compare($rhs) > 0 ) { + $quotient->value[$q_index]--; + + $lhs = new Math_BigInteger(); + $lhs->value = array($quotient->value[$q_index]); + $lhs = $lhs->multiply($temp); + } + + $adjust = $this->_array_repeat(0, $q_index); + $temp = new Math_BigInteger(); + $temp->value = array($quotient->value[$q_index]); + $temp = $temp->multiply($y); + $temp->value = array_merge($adjust, $temp->value); + + $x = $x->subtract($temp); + + if ($x->compare($zero) < 0) { + $temp->value = array_merge($adjust, $y->value); + $x = $x->add($temp); + + $quotient->value[$q_index]--; + } + + $x_max = count($x->value) - 1; + } + + // unnormalize the remainder + $x->_rshift($shift); + + $quotient->is_negative = $x_sign != $y_sign; + + // calculate the "common residue", if appropriate + if ( $x_sign ) { + $y->_rshift($shift); + $x = $y->subtract($x); + } + + return array($this->_normalize($quotient), $this->_normalize($x)); + } + + /** + * Divides a BigInteger by a regular integer + * + * abc / x = a00 / x + b0 / x + c / x + * + * @param Math_BigInteger $divisor + * @return Array + * @access public + */ + function _divide_digit($divisor) + { + $carry = 0; + $result = new Math_BigInteger(); + + for ($i = count($this->value) - 1; $i >= 0; $i--) { + $temp = 0x4000000 * $carry + $this->value[$i]; + $result->value[$i] = floor($temp / $divisor); + $carry = fmod($temp, $divisor); + } + + $remainder = new Math_BigInteger(); + $remainder->value = array($carry); + + return array($result, $remainder); + } + + /** + * Performs modular exponentiation. + * + * Here's an example: + * + * modPow($b, $c); + * + * echo $c->toString(); // outputs 10 + * ?> + * + * + * @param Math_BigInteger $e + * @param Math_BigInteger $n + * @return Math_BigInteger + * @access public + * @internal The most naive approach to modular exponentiation has very unreasonable requirements, and + * and although the approach involving repeated squaring does vastly better, it, too, is impractical + * for our purposes. The reason being that division - by far the most complicated and time-consuming + * of the basic operations (eg. +,-,*,/) - occurs multiple times within it. + * + * Modular reductions resolve this issue. Although an individual modular reduction takes more time + * then an individual division, when performed in succession (with the same modulo), they're a lot faster. + * + * The two most commonly used modular reductions are Barrett and Montgomery reduction. Montgomery reduction, + * although faster, only works when the gcd of the modulo and of the base being used is 1. In RSA, when the + * base is a power of two, the modulo - a product of two primes - is always going to have a gcd of 1 (because + * the product of two odd numbers is odd), but what about when RSA isn't used? + * + * In contrast, Barrett reduction has no such constraint. As such, some bigint implementations perform a + * Barrett reduction after every operation in the modpow function. Others perform Barrett reductions when the + * modulo is even and Montgomery reductions when the modulo is odd. BigInteger.java's modPow method, however, + * uses a trick involving the Chinese Remainder Theorem to factor the even modulo into two numbers - one odd and + * the other, a power of two - and recombine them, later. This is the method that this modPow function uses. + * {@link http://islab.oregonstate.edu/papers/j34monex.pdf Montgomery Reduction with Even Modulus} elaborates. + */ + function modPow($e, $n) + { + $n = $this->bitmask !== false && $this->bitmask->compare($n) < 0 ? $this->bitmask : $n->abs(); + + if ($e->compare(new Math_BigInteger()) < 0) { + $e = $e->abs(); + + $temp = $this->modInverse($n); + if ($temp === false) { + return false; + } + + return $this->_normalize($temp->modPow($e, $n)); + } + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_powm($this->value, $e->value, $n->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $temp = new Math_BigInteger(); + $temp->value = bcpowmod($this->value, $e->value, $n->value); + + return $this->_normalize($temp); + } + + if ( empty($e->value) ) { + $temp = new Math_BigInteger(); + $temp->value = array(1); + return $this->_normalize($temp); + } + + if ( $e->value == array(1) ) { + list(, $temp) = $this->divide($n); + return $this->_normalize($temp); + } + + if ( $e->value == array(2) ) { + $temp = $this->_square(); + list(, $temp) = $temp->divide($n); + return $this->_normalize($temp); + } + + return $this->_normalize($this->_slidingWindow($e, $n, MATH_BIGINTEGER_BARRETT)); + + // is the modulo odd? + if ( $n->value[0] & 1 ) { + return $this->_normalize($this->_slidingWindow($e, $n, MATH_BIGINTEGER_MONTGOMERY)); + } + // if it's not, it's even + + // find the lowest set bit (eg. the max pow of 2 that divides $n) + for ($i = 0; $i < count($n->value); $i++) { + if ( $n->value[$i] ) { + $temp = decbin($n->value[$i]); + $j = strlen($temp) - strrpos($temp, '1') - 1; + $j+= 26 * $i; + break; + } + } + // at this point, 2^$j * $n/(2^$j) == $n + + $mod1 = $n->copy(); + $mod1->_rshift($j); + $mod2 = new Math_BigInteger(); + $mod2->value = array(1); + $mod2->_lshift($j); + + $part1 = ( $mod1->value != array(1) ) ? $this->_slidingWindow($e, $mod1, MATH_BIGINTEGER_MONTGOMERY) : new Math_BigInteger(); + $part2 = $this->_slidingWindow($e, $mod2, MATH_BIGINTEGER_POWEROF2); + + $y1 = $mod2->modInverse($mod1); + $y2 = $mod1->modInverse($mod2); + + $result = $part1->multiply($mod2); + $result = $result->multiply($y1); + + $temp = $part2->multiply($mod1); + $temp = $temp->multiply($y2); + + $result = $result->add($temp); + list(, $result) = $result->divide($n); + + return $this->_normalize($result); + } + + /** + * Performs modular exponentiation. + * + * Alias for Math_BigInteger::modPow() + * + * @param Math_BigInteger $e + * @param Math_BigInteger $n + * @return Math_BigInteger + * @access public + */ + function powMod($e, $n) + { + return $this->modPow($e, $n); + } + + /** + * Sliding Window k-ary Modular Exponentiation + * + * Based on {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=27 HAC 14.85} / + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=210 MPM 7.7}. In a departure from those algorithims, + * however, this function performs a modular reduction after every multiplication and squaring operation. + * As such, this function has the same preconditions that the reductions being used do. + * + * @param Math_BigInteger $e + * @param Math_BigInteger $n + * @param Integer $mode + * @return Math_BigInteger + * @access private + */ + function _slidingWindow($e, $n, $mode) + { + static $window_ranges = array(7, 25, 81, 241, 673, 1793); // from BigInteger.java's oddModPow function + //static $window_ranges = array(0, 7, 36, 140, 450, 1303, 3529); // from MPM 7.3.1 + + $e_length = count($e->value) - 1; + $e_bits = decbin($e->value[$e_length]); + for ($i = $e_length - 1; $i >= 0; $i--) { + $e_bits.= str_pad(decbin($e->value[$i]), 26, '0', STR_PAD_LEFT); + } + + $e_length = strlen($e_bits); + + // calculate the appropriate window size. + // $window_size == 3 if $window_ranges is between 25 and 81, for example. + for ($i = 0, $window_size = 1; $e_length > $window_ranges[$i] && $i < count($window_ranges); $window_size++, $i++); + switch ($mode) { + case MATH_BIGINTEGER_MONTGOMERY: + $reduce = '_montgomery'; + $prep = '_prepMontgomery'; + break; + case MATH_BIGINTEGER_BARRETT: + $reduce = '_barrett'; + $prep = '_barrett'; + break; + case MATH_BIGINTEGER_POWEROF2: + $reduce = '_mod2'; + $prep = '_mod2'; + break; + case MATH_BIGINTEGER_CLASSIC: + $reduce = '_remainder'; + $prep = '_remainder'; + break; + case MATH_BIGINTEGER_NONE: + // ie. do no modular reduction. useful if you want to just do pow as opposed to modPow. + $reduce = 'copy'; + $prep = 'copy'; + break; + default: + // an invalid $mode was provided + } + + // precompute $this^0 through $this^$window_size + $powers = array(); + $powers[1] = $this->$prep($n); + $powers[2] = $powers[1]->_square(); + $powers[2] = $powers[2]->$reduce($n); + + // we do every other number since substr($e_bits, $i, $j+1) (see below) is supposed to end + // in a 1. ie. it's supposed to be odd. + $temp = 1 << ($window_size - 1); + for ($i = 1; $i < $temp; $i++) { + $powers[2 * $i + 1] = $powers[2 * $i - 1]->multiply($powers[2]); + $powers[2 * $i + 1] = $powers[2 * $i + 1]->$reduce($n); + } + + $result = new Math_BigInteger(); + $result->value = array(1); + $result = $result->$prep($n); + + for ($i = 0; $i < $e_length; ) { + if ( !$e_bits[$i] ) { + $result = $result->_square(); + $result = $result->$reduce($n); + $i++; + } else { + for ($j = $window_size - 1; $j > 0; $j--) { + if ( !empty($e_bits[$i + $j]) ) { + break; + } + } + + for ($k = 0; $k <= $j; $k++) {// eg. the length of substr($e_bits, $i, $j+1) + $result = $result->_square(); + $result = $result->$reduce($n); + } + + $result = $result->multiply($powers[bindec(substr($e_bits, $i, $j + 1))]); + $result = $result->$reduce($n); + + $i+=$j + 1; + } + } + + $result = $result->$reduce($n); + + return $result; + } + + /** + * Remainder + * + * A wrapper for the divide function. + * + * @see divide() + * @see _slidingWindow() + * @access private + * @param Math_BigInteger + * @return Math_BigInteger + */ + function _remainder($n) + { + list(, $temp) = $this->divide($n); + return $temp; + } + + /** + * Modulos for Powers of Two + * + * Calculates $x%$n, where $n = 2**$e, for some $e. Since this is basically the same as doing $x & ($n-1), + * we'll just use this function as a wrapper for doing that. + * + * @see _slidingWindow() + * @access private + * @param Math_BigInteger + * @return Math_BigInteger + */ + function _mod2($n) + { + $temp = new Math_BigInteger(); + $temp->value = array(1); + return $this->bitwise_and($n->subtract($temp)); + } + + /** + * Barrett Modular Reduction + * + * See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=14 HAC 14.3.3} / + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=165 MPM 6.2.5} for more information. Modified slightly, + * so as not to require negative numbers (initially, this script didn't support negative numbers). + * + * @see _slidingWindow() + * @access private + * @param Math_BigInteger + * @return Math_BigInteger + */ + function _barrett($n) + { + static $cache = array( + MATH_BIGINTEGER_VARIABLE => array(), + MATH_BIGINTEGER_DATA => array() + ); + + $n_length = count($n->value); + + if (count($this->value) > 2 * $n_length) { + list(, $temp) = $this->divide($n); + return $temp; + } + + if ( ($key = array_search($n->value, $cache[MATH_BIGINTEGER_VARIABLE])) === false ) { + $key = count($cache[MATH_BIGINTEGER_VARIABLE]); + $cache[MATH_BIGINTEGER_VARIABLE][] = $n->value; + $temp = new Math_BigInteger(); + $temp->value = $this->_array_repeat(0, 2 * $n_length); + $temp->value[] = 1; + list($cache[MATH_BIGINTEGER_DATA][], ) = $temp->divide($n); + } + + $temp = new Math_BigInteger(); + $temp->value = array_slice($this->value, $n_length - 1); + $temp = $temp->multiply($cache[MATH_BIGINTEGER_DATA][$key]); + $temp->value = array_slice($temp->value, $n_length + 1); + + $result = new Math_BigInteger(); + $result->value = array_slice($this->value, 0, $n_length + 1); + $temp = $temp->_multiplyLower($n, $n_length + 1); + // $temp->value == array_slice($temp->multiply($n)->value, 0, $n_length + 1) + + if ($result->compare($temp) < 0) { + $corrector = new Math_BigInteger(); + $corrector->value = $this->_array_repeat(0, $n_length + 1); + $corrector->value[] = 1; + $result = $result->add($corrector); + } + + $result = $result->subtract($temp); + while ($result->compare($n) > 0) { + $result = $result->subtract($n); + } + + return $result; + } + + /** + * Montgomery Modular Reduction + * + * ($this->_prepMontgomery($n))->_montgomery($n) yields $x%$n. + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=170 MPM 6.3} provides insights on how this can be + * improved upon (basically, by using the comba method). gcd($n, 2) must be equal to one for this function + * to work correctly. + * + * @see _prepMontgomery() + * @see _slidingWindow() + * @access private + * @param Math_BigInteger + * @return Math_BigInteger + */ + function _montgomery($n) + { + static $cache = array( + MATH_BIGINTEGER_VARIABLE => array(), + MATH_BIGINTEGER_DATA => array() + ); + + if ( ($key = array_search($n->value, $cache[MATH_BIGINTEGER_VARIABLE])) === false ) { + $key = count($cache[MATH_BIGINTEGER_VARIABLE]); + $cache[MATH_BIGINTEGER_VARIABLE][] = $n->value; + $cache[MATH_BIGINTEGER_DATA][] = $n->_modInverse67108864(); + } + + $k = count($n->value); + + $result = $this->copy(); + + for ($i = 0; $i < $k; $i++) { + $temp = new Math_BigInteger(); + $temp->value = array( + ($result->value[$i] * $cache[MATH_BIGINTEGER_DATA][$key]) & 0x3FFFFFF + ); + + $temp = $temp->multiply($n); + $temp->value = array_merge($this->_array_repeat(0, $i), $temp->value); + $result = $result->add($temp); + } + + $result->value = array_slice($result->value, $k); + + if ($result->compare($n) >= 0) { + $result = $result->subtract($n); + } + + return $result; + } + + /** + * Prepare a number for use in Montgomery Modular Reductions + * + * @see _montgomery() + * @see _slidingWindow() + * @access private + * @param Math_BigInteger + * @return Math_BigInteger + */ + function _prepMontgomery($n) + { + $k = count($n->value); + + $temp = new Math_BigInteger(); + $temp->value = array_merge($this->_array_repeat(0, $k), $this->value); + + list(, $temp) = $temp->divide($n); + return $temp; + } + + /** + * Modular Inverse of a number mod 2**26 (eg. 67108864) + * + * Based off of the bnpInvDigit function implemented and justified in the following URL: + * + * {@link http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn.js} + * + * The following URL provides more info: + * + * {@link http://groups.google.com/group/sci.crypt/msg/7a137205c1be7d85} + * + * As for why we do all the bitmasking... strange things can happen when converting from floats to ints. For + * instance, on some computers, var_dump((int) -4294967297) yields int(-1) and on others, it yields + * int(-2147483648). To avoid problems stemming from this, we use bitmasks to guarantee that ints aren't + * auto-converted to floats. The outermost bitmask is present because without it, there's no guarantee that + * the "residue" returned would be the so-called "common residue". We use fmod, in the last step, because the + * maximum possible $x is 26 bits and the maximum $result is 16 bits. Thus, we have to be able to handle up to + * 40 bits, which only 64-bit floating points will support. + * + * Thanks to Pedro Gimeno Fortea for input! + * + * @see _montgomery() + * @access private + * @return Integer + */ + function _modInverse67108864() // 2**26 == 67108864 + { + $x = -$this->value[0]; + $result = $x & 0x3; // x**-1 mod 2**2 + $result = ($result * (2 - $x * $result)) & 0xF; // x**-1 mod 2**4 + $result = ($result * (2 - ($x & 0xFF) * $result)) & 0xFF; // x**-1 mod 2**8 + $result = ($result * ((2 - ($x & 0xFFFF) * $result) & 0xFFFF)) & 0xFFFF; // x**-1 mod 2**16 + $result = fmod($result * (2 - fmod($x * $result, 0x4000000)), 0x4000000); // x**-1 mod 2**26 + return $result & 0x3FFFFFF; + } + + /** + * Calculates modular inverses. + * + * Say you have (30 mod 17 * x mod 17) mod 17 == 1. x can be found using modular inverses. + * + * Here's an example: + * + * modInverse($b); + * echo $c->toString(); // outputs 4 + * + * echo "\r\n"; + * + * $d = $a->multiply($c); + * list(, $d) = $d->divide($b); + * echo $d; // outputs 1 (as per the definition of modular inverse) + * ?> + * + * + * @param Math_BigInteger $n + * @return mixed false, if no modular inverse exists, Math_BigInteger, otherwise. + * @access public + * @internal See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=21 HAC 14.64} for more information. + */ + function modInverse($n) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_invert($this->value, $n->value); + + return ( $temp->value === false ) ? false : $this->_normalize($temp); + } + + static $zero, $one; + if (!isset($zero)) { + $zero = new Math_BigInteger(); + $one = new Math_BigInteger(1); + } + + // $x mod $n == $x mod -$n. + $n = $n->abs(); + + if ($this->compare($zero) < 0) { + $temp = $this->abs(); + $temp = $temp->modInverse($n); + return $negated === false ? false : $this->_normalize($n->subtract($temp)); + } + + extract($this->extendedGCD($n)); + + if (!$gcd->equals($one)) { + return false; + } + + $x = $x->compare($zero) < 0 ? $x->add($n) : $x; + + return $this->compare($zero) < 0 ? $this->_normalize($n->subtract($x)) : $this->_normalize($x); + } + + /** + * Calculates the greatest common divisor and Bézout's identity. + * + * Say you have 693 and 609. The GCD is 21. Bézout's identity states that there exist integers x and y such that + * 693*x + 609*y == 21. In point of fact, there are actually an infinite number of x and y combinations and which + * combination is returned is dependant upon which mode is in use. See + * {@link http://en.wikipedia.org/wiki/B%C3%A9zout%27s_identity Bézout's identity - Wikipedia} for more information. + * + * Here's an example: + * + * extendedGCD($b)); + * + * echo $gcd->toString() . "\r\n"; // outputs 21 + * echo $a->toString() * $x->toString() + $b->toString() * $y->toString(); // outputs 21 + * ?> + * + * + * @param Math_BigInteger $n + * @return Math_BigInteger + * @access public + * @internal Calculates the GCD using the binary xGCD algorithim described in + * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=19 HAC 14.61}. As the text above 14.61 notes, + * the more traditional algorithim requires "relatively costly multiple-precision divisions". + */ + function extendedGCD($n) { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + extract(gmp_gcdext($this->value, $n->value)); + + return array( + 'gcd' => $this->_normalize(new Math_BigInteger($g)), + 'x' => $this->_normalize(new Math_BigInteger($s)), + 'y' => $this->_normalize(new Math_BigInteger($t)) + ); + case MATH_BIGINTEGER_MODE_BCMATH: + // it might be faster to use the binary xGCD algorithim here, as well, but (1) that algorithim works + // best when the base is a power of 2 and (2) i don't think it'd make much difference, anyway. as is, + // the basic extended euclidean algorithim is what we're using. + + $u = $this->value; + $v = $n->value; + + $a = '1'; + $b = '0'; + $c = '0'; + $d = '1'; + + while (bccomp($v, '0') != 0) { + $q = bcdiv($u, $v); + + $temp = $u; + $u = $v; + $v = bcsub($temp, bcmul($v, $q)); + + $temp = $a; + $a = $c; + $c = bcsub($temp, bcmul($a, $q)); + + $temp = $b; + $b = $d; + $d = bcsub($temp, bcmul($b, $q)); + } + + return array( + 'gcd' => $this->_normalize(new Math_BigInteger($u)), + 'x' => $this->_normalize(new Math_BigInteger($a)), + 'y' => $this->_normalize(new Math_BigInteger($b)) + ); + } + + $y = $n->copy(); + $x = $this->copy(); + $g = new Math_BigInteger(); + $g->value = array(1); + + while ( !(($x->value[0] & 1)|| ($y->value[0] & 1)) ) { + $x->_rshift(1); + $y->_rshift(1); + $g->_lshift(1); + } + + $u = $x->copy(); + $v = $y->copy(); + + $a = new Math_BigInteger(); + $b = new Math_BigInteger(); + $c = new Math_BigInteger(); + $d = new Math_BigInteger(); + + $a->value = $d->value = $g->value = array(1); + + while ( !empty($u->value) ) { + while ( !($u->value[0] & 1) ) { + $u->_rshift(1); + if ( ($a->value[0] & 1) || ($b->value[0] & 1) ) { + $a = $a->add($y); + $b = $b->subtract($x); + } + $a->_rshift(1); + $b->_rshift(1); + } + + while ( !($v->value[0] & 1) ) { + $v->_rshift(1); + if ( ($c->value[0] & 1) || ($d->value[0] & 1) ) { + $c = $c->add($y); + $d = $d->subtract($x); + } + $c->_rshift(1); + $d->_rshift(1); + } + + if ($u->compare($v) >= 0) { + $u = $u->subtract($v); + $a = $a->subtract($c); + $b = $b->subtract($d); + } else { + $v = $v->subtract($u); + $c = $c->subtract($a); + $d = $d->subtract($b); + } + } + + return array( + 'gcd' => $this->_normalize($g->multiply($v)), + 'x' => $this->_normalize($c), + 'y' => $this->_normalize($d) + ); + } + + /** + * Calculates the greatest common divisor + * + * Say you have 693 and 609. The GCD is 21. + * + * Here's an example: + * + * extendedGCD($b); + * + * echo $gcd->toString() . "\r\n"; // outputs 21 + * ?> + * + * + * @param Math_BigInteger $n + * @return Math_BigInteger + * @access public + */ + function gcd($n) + { + extract($this->extendedGCD($n)); + return $gcd; + } + + /** + * Absolute value. + * + * @return Math_BigInteger + * @access public + */ + function abs() + { + $temp = new Math_BigInteger(); + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp->value = gmp_abs($this->value); + break; + case MATH_BIGINTEGER_MODE_BCMATH: + $temp->value = (bccomp($this->value, '0') < 0) ? substr($this->value, 1) : $this->value; + break; + default: + $temp->value = $this->value; + } + + return $temp; + } + + /** + * Compares two numbers. + * + * Although one might think !$x->compare($y) means $x != $y, it, in fact, means the opposite. The reason for this is + * demonstrated thusly: + * + * $x > $y: $x->compare($y) > 0 + * $x < $y: $x->compare($y) < 0 + * $x == $y: $x->compare($y) == 0 + * + * Note how the same comparison operator is used. If you want to test for equality, use $x->equals($y). + * + * @param Math_BigInteger $x + * @return Integer < 0 if $this is less than $x; > 0 if $this is greater than $x, and 0 if they are equal. + * @access public + * @see equals() + * @internal Could return $this->sub($x), but that's not as fast as what we do do. + */ + function compare($y) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + return gmp_cmp($this->value, $y->value); + case MATH_BIGINTEGER_MODE_BCMATH: + return bccomp($this->value, $y->value); + } + + $x = $this->_normalize($this->copy()); + $y = $this->_normalize($y); + + if ( $x->is_negative != $y->is_negative ) { + return ( !$x->is_negative && $y->is_negative ) ? 1 : -1; + } + + $result = $x->is_negative ? -1 : 1; + + if ( count($x->value) != count($y->value) ) { + return ( count($x->value) > count($y->value) ) ? $result : -$result; + } + + for ($i = count($x->value) - 1; $i >= 0; $i--) { + if ($x->value[$i] != $y->value[$i]) { + return ( $x->value[$i] > $y->value[$i] ) ? $result : -$result; + } + } + + return 0; + } + + /** + * Tests the equality of two numbers. + * + * If you need to see if one number is greater than or less than another number, use Math_BigInteger::compare() + * + * @param Math_BigInteger $x + * @return Boolean + * @access public + * @see compare() + */ + function equals($x) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + return gmp_cmp($this->value, $x->value) == 0; + default: + return $this->value == $x->value && $this->is_negative == $x->is_negative; + } + } + + /** + * Set Precision + * + * Some bitwise operations give different results depending on the precision being used. Examples include left + * shift, not, and rotates. + * + * @param Math_BigInteger $x + * @access public + * @return Math_BigInteger + */ + function setPrecision($bits) + { + $this->precision = $bits; + if ( MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_BCMATH ) { + $this->bitmask = new Math_BigInteger(chr((1 << ($bits & 0x7)) - 1) . str_repeat(chr(0xFF), $bits >> 3), 256); + } else { + $this->bitmask = new Math_BigInteger(bcpow('2', $bits)); + } + } + + /** + * Logical And + * + * @param Math_BigInteger $x + * @access public + * @internal Implemented per a request by Lluis Pamies i Juarez + * @return Math_BigInteger + */ + function bitwise_and($x) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_and($this->value, $x->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $left = $this->toBytes(); + $right = $x->toBytes(); + + $length = max(strlen($left), strlen($right)); + + $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); + $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); + + return $this->_normalize(new Math_BigInteger($left & $right, 256)); + } + + $result = $this->copy(); + + $length = min(count($x->value), count($this->value)); + + $result->value = array_slice($result->value, 0, $length); + + for ($i = 0; $i < $length; $i++) { + $result->value[$i] = $result->value[$i] & $x->value[$i]; + } + + return $this->_normalize($result); + } + + /** + * Logical Or + * + * @param Math_BigInteger $x + * @access public + * @internal Implemented per a request by Lluis Pamies i Juarez + * @return Math_BigInteger + */ + function bitwise_or($x) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_or($this->value, $x->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $left = $this->toBytes(); + $right = $x->toBytes(); + + $length = max(strlen($left), strlen($right)); + + $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); + $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); + + return $this->_normalize(new Math_BigInteger($left | $right, 256)); + } + + $length = max(count($this->value), count($x->value)); + $result = $this->copy(); + $result->value = array_pad($result->value, 0, $length); + $x->value = array_pad($x->value, 0, $length); + + for ($i = 0; $i < $length; $i++) { + $result->value[$i] = $this->value[$i] | $x->value[$i]; + } + + return $this->_normalize($result); + } + + /** + * Logical Exclusive-Or + * + * @param Math_BigInteger $x + * @access public + * @internal Implemented per a request by Lluis Pamies i Juarez + * @return Math_BigInteger + */ + function bitwise_xor($x) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_xor($this->value, $x->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $left = $this->toBytes(); + $right = $x->toBytes(); + + $length = max(strlen($left), strlen($right)); + + $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); + $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); + + return $this->_normalize(new Math_BigInteger($left ^ $right, 256)); + } + + $length = max(count($this->value), count($x->value)); + $result = $this->copy(); + $result->value = array_pad($result->value, 0, $length); + $x->value = array_pad($x->value, 0, $length); + + for ($i = 0; $i < $length; $i++) { + $result->value[$i] = $this->value[$i] ^ $x->value[$i]; + } + + return $this->_normalize($result); + } + + /** + * Logical Not + * + * @access public + * @internal Implemented per a request by Lluis Pamies i Juarez + * @return Math_BigInteger + */ + function bitwise_not() + { + // calculuate "not" without regard to $this->precision + // (will always result in a smaller number. ie. ~1 isn't 1111 1110 - it's 0) + $temp = $this->toBytes(); + $pre_msb = decbin(ord($temp[0])); + $temp = ~$temp; + $msb = decbin(ord($temp[0])); + if (strlen($msb) == 8) { + $msb = substr($msb, strpos($msb, '0')); + } + $temp[0] = chr(bindec($msb)); + + // see if we need to add extra leading 1's + $current_bits = strlen($pre_msb) + 8 * strlen($temp) - 8; + $new_bits = $this->precision - $current_bits; + if ($new_bits <= 0) { + return $this->_normalize(new Math_BigInteger($temp, 256)); + } + + // generate as many leading 1's as we need to. + $leading_ones = chr((1 << ($new_bits & 0x7)) - 1) . str_repeat(chr(0xFF), $new_bits >> 3); + $this->_base256_lshift($leading_ones, $current_bits); + + $temp = str_pad($temp, ceil($this->bits / 8), chr(0), STR_PAD_LEFT); + + return $this->_normalize(new Math_BigInteger($leading_ones | $temp, 256)); + } + + /** + * Logical Right Shift + * + * Shifts BigInteger's by $shift bits, effectively dividing by 2**$shift. + * + * @param Integer $shift + * @return Math_BigInteger + * @access public + * @internal The only version that yields any speed increases is the internal version. + */ + function bitwise_rightShift($shift) + { + $temp = new Math_BigInteger(); + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + static $two; + + if (empty($two)) { + $two = gmp_init('2'); + } + + $temp->value = gmp_div_q($this->value, gmp_pow($two, $shift)); + + break; + case MATH_BIGINTEGER_MODE_BCMATH: + $temp->value = bcdiv($this->value, bcpow('2', $shift)); + + break; + default: // could just replace _lshift with this, but then all _lshift() calls would need to be rewritten + // and I don't want to do that... + $temp->value = $this->value; + $temp->_rshift($shift); + } + + return $this->_normalize($temp); + } + + /** + * Logical Left Shift + * + * Shifts BigInteger's by $shift bits, effectively multiplying by 2**$shift. + * + * @param Integer $shift + * @return Math_BigInteger + * @access public + * @internal The only version that yields any speed increases is the internal version. + */ + function bitwise_leftShift($shift) + { + $temp = new Math_BigInteger(); + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + static $two; + + if (empty($two)) { + $two = gmp_init('2'); + } + + $temp->value = gmp_mul($this->value, gmp_pow($two, $shift)); + + break; + case MATH_BIGINTEGER_MODE_BCMATH: + $temp->value = bcmul($this->value, bcpow('2', $shift)); + + break; + default: // could just replace _rshift with this, but then all _lshift() calls would need to be rewritten + // and I don't want to do that... + $temp->value = $this->value; + $temp->_lshift($shift); + } + + return $this->_normalize($temp); + } + + /** + * Logical Left Rotate + * + * Instead of the top x bits being dropped they're appended to the shifted bit string. + * + * @param Integer $shift + * @return Math_BigInteger + * @access public + */ + function bitwise_leftRotate($shift) + { + $bits = $this->toBytes(); + + if ($this->precision > 0) { + $precision = $this->precision; + if ( MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_BCMATH ) { + $mask = $this->bitmask->subtract(new Math_BigInteger(1)); + $mask = $mask->toBytes(); + } else { + $mask = $this->bitmask->toBytes(); + } + } else { + $temp = ord($bits[0]); + for ($i = 0; $temp >> $i; $i++); + $precision = 8 * strlen($bits) - 8 + $i; + $mask = chr((1 << ($precision & 0x7)) - 1) . str_repeat(chr(0xFF), $precision >> 3); + } + + if ($shift < 0) { + $shift+= $precision; + } + $shift%= $precision; + + if (!$shift) { + return $this->copy(); + } + + $left = $this->bitwise_leftShift($shift); + $left = $left->bitwise_and(new Math_BigInteger($mask, 256)); + $right = $this->bitwise_rightShift($precision - $shift); + $result = MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_BCMATH ? $left->bitwise_or($right) : $left->add($right); + return $this->_normalize($result); + } + + /** + * Logical Right Rotate + * + * Instead of the bottom x bits being dropped they're prepended to the shifted bit string. + * + * @param Integer $shift + * @return Math_BigInteger + * @access public + */ + function bitwise_rightRotate($shift) + { + return $this->bitwise_leftRotate(-$shift); + } + + /** + * Set random number generator function + * + * $generator should be the name of a random generating function whose first parameter is the minimum + * value and whose second parameter is the maximum value. If this function needs to be seeded, it should + * be seeded prior to calling Math_BigInteger::random() or Math_BigInteger::randomPrime() + * + * If the random generating function is not explicitly set, it'll be assumed to be mt_rand(). + * + * @see random() + * @see randomPrime() + * @param optional String $generator + * @access public + */ + function setRandomGenerator($generator) + { + $this->generator = $generator; + } + + /** + * Generate a random number + * + * @param optional Integer $min + * @param optional Integer $max + * @return Math_BigInteger + * @access public + */ + function random($min = false, $max = false) + { + if ($min === false) { + $min = new Math_BigInteger(0); + } + + if ($max === false) { + $max = new Math_BigInteger(0x7FFFFFFF); + } + + $compare = $max->compare($min); + + if (!$compare) { + return $this->_normalize($min); + } else if ($compare < 0) { + // if $min is bigger then $max, swap $min and $max + $temp = $max; + $max = $min; + $min = $temp; + } + + $generator = $this->generator; + + $max = $max->subtract($min); + $max = ltrim($max->toBytes(), chr(0)); + $size = strlen($max) - 1; + $random = ''; + + $bytes = $size & 1; + for ($i = 0; $i < $bytes; $i++) { + $random.= chr($generator(0, 255)); + } + + $blocks = $size >> 1; + for ($i = 0; $i < $blocks; $i++) { + // mt_rand(-2147483648, 0x7FFFFFFF) always produces -2147483648 on some systems + $random.= pack('n', $generator(0, 0xFFFF)); + } + + $temp = new Math_BigInteger($random, 256); + if ($temp->compare(new Math_BigInteger(substr($max, 1), 256)) > 0) { + $random = chr($generator(0, ord($max[0]) - 1)) . $random; + } else { + $random = chr($generator(0, ord($max[0]) )) . $random; + } + + $random = new Math_BigInteger($random, 256); + + return $this->_normalize($random->add($min)); + } + + /** + * Generate a random prime number. + * + * If there's not a prime within the given range, false will be returned. If more than $timeout seconds have elapsed, + * give up and return false. + * + * @param optional Integer $min + * @param optional Integer $max + * @param optional Integer $timeout + * @return Math_BigInteger + * @access public + * @internal See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap4.pdf#page=15 HAC 4.44}. + */ + function randomPrime($min = false, $max = false, $timeout = false) + { + // gmp_nextprime() requires PHP 5 >= 5.2.0 per . + if ( MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_GMP && function_exists('gmp_nextprime') ) { + // we don't rely on Math_BigInteger::random()'s min / max when gmp_nextprime() is being used since this function + // does its own checks on $max / $min when gmp_nextprime() is used. When gmp_nextprime() is not used, however, + // the same $max / $min checks are not performed. + if ($min === false) { + $min = new Math_BigInteger(0); + } + + if ($max === false) { + $max = new Math_BigInteger(0x7FFFFFFF); + } + + $compare = $max->compare($min); + + if (!$compare) { + return $min; + } else if ($compare < 0) { + // if $min is bigger then $max, swap $min and $max + $temp = $max; + $max = $min; + $min = $temp; + } + + $x = $this->random($min, $max); + + $x->value = gmp_nextprime($x->value); + + if ($x->compare($max) <= 0) { + return $x; + } + + $x->value = gmp_nextprime($min->value); + + if ($x->compare($max) <= 0) { + return $x; + } + + return false; + } + + $repeat1 = $repeat2 = array(); + + $one = new Math_BigInteger(1); + $two = new Math_BigInteger(2); + + $start = time(); + + do { + if ($timeout !== false && time() - $start > $timeout) { + return false; + } + + $x = $this->random($min, $max); + if ($x->equals($two)) { + return $x; + } + + // make the number odd + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + gmp_setbit($x->value, 0); + break; + case MATH_BIGINTEGER_MODE_BCMATH: + if ($x->value[strlen($x->value) - 1] % 2 == 0) { + $x = $x->add($one); + } + break; + default: + $x->value[0] |= 1; + } + + // if we've seen this number twice before, assume there are no prime numbers within the given range + if (in_array($x->value, $repeat1)) { + if (in_array($x->value, $repeat2)) { + return false; + } else { + $repeat2[] = $x->value; + } + } else { + $repeat1[] = $x->value; + } + } while (!$x->isPrime()); + + return $x; + } + + /** + * Checks a numer to see if it's prime + * + * Assuming the $t parameter is not set, this functoin has an error rate of 2**-80. The main motivation for the + * $t parameter is distributability. Math_BigInteger::randomPrime() can be distributed accross multiple pageloads + * on a website instead of just one. + * + * @param optional Integer $t + * @return Boolean + * @access public + * @internal Uses the + * {@link http://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test Miller–Rabin primality test}. See + * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap4.pdf#page=8 HAC 4.24}. + */ + function isPrime($t = false) + { + $length = strlen($this->toBytes()); + + if (!$t) { + // see HAC 4.49 "Note (controlling the error probability)" + if ($length >= 163) { $t = 2; } // floor(1300 / 8) + else if ($length >= 106) { $t = 3; } // floor( 850 / 8) + else if ($length >= 81 ) { $t = 4; } // floor( 650 / 8) + else if ($length >= 68 ) { $t = 5; } // floor( 550 / 8) + else if ($length >= 56 ) { $t = 6; } // floor( 450 / 8) + else if ($length >= 50 ) { $t = 7; } // floor( 400 / 8) + else if ($length >= 43 ) { $t = 8; } // floor( 350 / 8) + else if ($length >= 37 ) { $t = 9; } // floor( 300 / 8) + else if ($length >= 31 ) { $t = 12; } // floor( 250 / 8) + else if ($length >= 25 ) { $t = 15; } // floor( 200 / 8) + else if ($length >= 18 ) { $t = 18; } // floor( 150 / 8) + else { $t = 27; } + } + + // ie. gmp_testbit($this, 0) + // ie. isEven() or !isOdd() + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + return gmp_prob_prime($this->value, $t) != 0; + case MATH_BIGINTEGER_MODE_BCMATH: + if ($this->value == '2') { + return true; + } + if ($this->value[strlen($this->value) - 1] % 2 == 0) { + return false; + } + break; + default: + if ($this->value == array(2)) { + return true; + } + if (~$this->value[0] & 1) { + return false; + } + } + + static $primes, $zero, $one, $two; + + if (!isset($primes)) { + $primes = array( + 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, + 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, + 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, + 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, + 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, + 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, + 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, + 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, + 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, + 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, + 953, 967, 971, 977, 983, 991, 997 + ); + + for ($i = 0; $i < count($primes); $i++) { + $primes[$i] = new Math_BigInteger($primes[$i]); + } + + $zero = new Math_BigInteger(); + $one = new Math_BigInteger(1); + $two = new Math_BigInteger(2); + } + + // see HAC 4.4.1 "Random search for probable primes" + for ($i = 0; $i < count($primes); $i++) { + list(, $r) = $this->divide($primes[$i]); + if ($r->equals($zero)) { + return false; + } + } + + $n = $this->copy(); + $n_1 = $n->subtract($one); + $n_2 = $n->subtract($two); + + $r = $n_1->copy(); + // ie. $s = gmp_scan1($n, 0) and $r = gmp_div_q($n, gmp_pow(gmp_init('2'), $s)); + if ( MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_BCMATH ) { + $s = 0; + while ($r->value[strlen($r->value) - 1] % 2 == 0) { + $r->value = bcdiv($r->value, 2); + $s++; + } + } else { + for ($i = 0; $i < count($r->value); $i++) { + $temp = ~$r->value[$i] & 0xFFFFFF; + for ($j = 1; ($temp >> $j) & 1; $j++); + if ($j != 25) { + break; + } + } + $s = 26 * $i + $j - 1; + $r->_rshift($s); + } + + for ($i = 0; $i < $t; $i++) { + $a = new Math_BigInteger(); + $a = $a->random($two, $n_2); + $y = $a->modPow($r, $n); + + if (!$y->equals($one) && !$y->equals($n_1)) { + for ($j = 1; $j < $s && !$y->equals($n_1); $j++) { + $y = $y->modPow($two, $n); + if ($y->equals($one)) { + return false; + } + } + + if (!$y->equals($n_1)) { + return false; + } + } + } + return true; + } + + /** + * Logical Left Shift + * + * Shifts BigInteger's by $shift bits. + * + * @param Integer $shift + * @access private + */ + function _lshift($shift) + { + if ( $shift == 0 ) { + return; + } + + $num_digits = floor($shift / 26); + $shift %= 26; + $shift = 1 << $shift; + + $carry = 0; + + for ($i = 0; $i < count($this->value); $i++) { + $temp = $this->value[$i] * $shift + $carry; + $carry = floor($temp / 0x4000000); + $this->value[$i] = $temp - $carry * 0x4000000; + } + + if ( $carry ) { + $this->value[] = $carry; + } + + while ($num_digits--) { + array_unshift($this->value, 0); + } + } + + /** + * Logical Right Shift + * + * Shifts BigInteger's by $shift bits. + * + * @param Integer $shift + * @access private + */ + function _rshift($shift) + { + if ($shift == 0) { + return; + } + + $num_digits = floor($shift / 26); + $shift %= 26; + $carry_shift = 26 - $shift; + $carry_mask = (1 << $shift) - 1; + + if ( $num_digits ) { + $this->value = array_slice($this->value, $num_digits); + } + + $carry = 0; + + for ($i = count($this->value) - 1; $i >= 0; $i--) { + $temp = $this->value[$i] >> $shift | $carry; + $carry = ($this->value[$i] & $carry_mask) << $carry_shift; + $this->value[$i] = $temp; + } + } + + /** + * Normalize + * + * Deletes leading zeros and truncates (if necessary) to maintain the appropriate precision + * + * @return Math_BigInteger + * @access private + */ + function _normalize($result) + { + $result->precision = $this->precision; + $result->bitmask = $this->bitmask; + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + if (!empty($result->bitmask->value)) { + $result->value = gmp_and($result->value, $result->bitmask->value); + } + + return $result; + case MATH_BIGINTEGER_MODE_BCMATH: + if (!empty($result->bitmask->value)) { + $result->value = bcmod($result->value, $result->bitmask->value); + } + + return $result; + } + + if ( !count($result->value) ) { + return $result; + } + + for ($i = count($result->value) - 1; $i >= 0; $i--) { + if ( $result->value[$i] ) { + break; + } + unset($result->value[$i]); + } + + if (!empty($result->bitmask->value)) { + $length = min(count($result->value), count($this->bitmask->value)); + $result->value = array_slice($result->value, 0, $length); + + for ($i = 0; $i < $length; $i++) { + $result->value[$i] = $result->value[$i] & $this->bitmask->value[$i]; + } + } + + return $result; + } + + /** + * Array Repeat + * + * @param $input Array + * @param $multiplier mixed + * @return Array + * @access private + */ + function _array_repeat($input, $multiplier) + { + return ($multiplier) ? array_fill(0, $multiplier, $input) : array(); + } + + /** + * Logical Left Shift + * + * Shifts binary strings $shift bits, essentially multiplying by 2**$shift. + * + * @param $x String + * @param $shift Integer + * @return String + * @access private + */ + function _base256_lshift(&$x, $shift) + { + if ($shift == 0) { + return; + } + + $num_bytes = $shift >> 3; // eg. floor($shift/8) + $shift &= 7; // eg. $shift % 8 + + $carry = 0; + for ($i = strlen($x) - 1; $i >= 0; $i--) { + $temp = ord($x[$i]) << $shift | $carry; + $x[$i] = chr($temp); + $carry = $temp >> 8; + } + $carry = ($carry != 0) ? chr($carry) : ''; + $x = $carry . $x . str_repeat(chr(0), $num_bytes); + } + + /** + * Logical Right Shift + * + * Shifts binary strings $shift bits, essentially dividing by 2**$shift and returning the remainder. + * + * @param $x String + * @param $shift Integer + * @return String + * @access private + */ + function _base256_rshift(&$x, $shift) + { + if ($shift == 0) { + $x = ltrim($x, chr(0)); + return ''; + } + + $num_bytes = $shift >> 3; // eg. floor($shift/8) + $shift &= 7; // eg. $shift % 8 + + $remainder = ''; + if ($num_bytes) { + $start = $num_bytes > strlen($x) ? -strlen($x) : -$num_bytes; + $remainder = substr($x, $start); + $x = substr($x, 0, -$num_bytes); + } + + $carry = 0; + $carry_shift = 8 - $shift; + for ($i = 0; $i < strlen($x); $i++) { + $temp = (ord($x[$i]) >> $shift) | $carry; + $carry = (ord($x[$i]) << $carry_shift) & 0xFF; + $x[$i] = chr($temp); + } + $x = ltrim($x, chr(0)); + + $remainder = chr($carry >> $carry_shift) . $remainder; + + return ltrim($remainder, chr(0)); + } + + // one quirk about how the following functions are implemented is that PHP defines N to be an unsigned long + // at 32-bits, while java's longs are 64-bits. + + /** + * Converts 32-bit integers to bytes. + * + * @param Integer $x + * @return String + * @access private + */ + function _int2bytes($x) + { + return ltrim(pack('N', $x), chr(0)); + } + + /** + * Converts bytes to 32-bit integers + * + * @param String $x + * @return Integer + * @access private + */ + function _bytes2int($x) + { + $temp = unpack('Nint', str_pad($x, 4, chr(0), STR_PAD_LEFT)); + return $temp['int']; + } +} \ No newline at end of file -- cgit v1.2.3-54-g00ecf From 23d44c7d5980fb096aeec62c600b71cfb9fac167 Mon Sep 17 00:00:00 2001 From: James Walker Date: Fri, 12 Mar 2010 19:34:45 -0500 Subject: converted sign, verify and fromString to new crypt lib --- plugins/OStatus/classes/Magicsig.php | 86 +++++++++++++----------------------- 1 file changed, 31 insertions(+), 55 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/classes/Magicsig.php b/plugins/OStatus/classes/Magicsig.php index b0a411e5d..d25b150e8 100644 --- a/plugins/OStatus/classes/Magicsig.php +++ b/plugins/OStatus/classes/Magicsig.php @@ -40,8 +40,9 @@ class Magicsig extends Memcached_DataObject public $keypair; public $alg; - private $_rsa; - + private $publicKey; + private $privateKey; + public function __construct($alg = 'RSA-SHA256') { $this->alg = $alg; @@ -101,15 +102,7 @@ class Magicsig extends Memcached_DataObject public function generate($user_id, $key_length = 512) { - PEAR::pushErrorHandling(PEAR_ERROR_RETURN); - - $keypair = new Crypt_RSA_KeyPair($key_length); - $params['public_key'] = $keypair->getPublicKey(); - $params['private_key'] = $keypair->getPrivateKey(); - - $this->_rsa = new Crypt_RSA($params); - PEAR::popErrorHandling(); - + // @fixme new key generation $this->user_id = $user_id; $this->insert(); } @@ -132,8 +125,6 @@ class Magicsig extends Memcached_DataObject public static function fromString($text) { - PEAR::pushErrorHandling(PEAR_ERROR_RETURN); - $magic_sig = new Magicsig(); // remove whitespace @@ -144,35 +135,40 @@ class Magicsig extends Memcached_DataObject return false; } - $mod = base64_url_decode($matches[1]); - $exp = base64_url_decode($matches[2]); + $mod = $matches[1]; + $exp = $matches[2]; if (!empty($matches[4])) { - $private_exp = base64_url_decode($matches[4]); + $private_exp = $matches[4]; } else { $private_exp = false; } - $params['public_key'] = new Crypt_RSA_KEY($mod, $exp, 'public'); - if ($params['public_key']->isError()) { - $error = $params['public_key']->getLastError(); - common_log(LOG_DEBUG, 'RSA Error: '. $error->getMessage()); - return false; - } + $magic_sig->loadKey($mod, $exp, 'public'); if ($private_exp) { - $params['private_key'] = new Crypt_RSA_KEY($mod, $private_exp, 'private'); - if ($params['private_key']->isError()) { - $error = $params['private_key']->getLastError(); - common_log(LOG_DEBUG, 'RSA Error: '. $error->getMessage()); - return false; - } + $magic_sig->loadKey($mod, $private_exp, 'private'); } - $magic_sig->_rsa = new Crypt_RSA($params); - PEAR::popErrorHandling(); - return $magic_sig; } + public function loadKey($mod, $exp, $type = 'public') + { + common_log(LOG_DEBUG, "Adding ".$type." key: (".$mod .', '. $exp .")"); + + $rsa = new Crypt_RSA(); + $rsa->signatureMode = CRYPT_RSA_SIGNATURE_PKCS1; + $rsa->setHash('sha256'); + $rsa->modulus = new Math_BigInteger(base64_url_decode($mod), 256); + $rsa->k = strlen($rsa->modulus->toBytes()); + $rsa->exponent = new Math_BigInteger(base64_url_decode($exp), 256); + + if ($type == 'private') { + $this->privateKey = $rsa; + } else { + $this->publicKey = $rsa; + } + } + public function getName() { return $this->alg; @@ -190,38 +186,18 @@ class Magicsig extends Memcached_DataObject public function sign($bytes) { - $hash = $this->getHash(); - $sig = $this->_rsa->createSign($bytes, null, $hash); - if ($this->_rsa->isError()) { - $error = $this->_rsa->getLastError(); - common_log(LOG_DEBUG, 'RSA Error: '. $error->getMessage()); - return false; - } - - return $sig; + $sig = $this->privateKey->sign($bytes); + return base64_url_encode($sig); } public function verify($signed_bytes, $signature) { - $hash = $this->getHash(); - $result = $this->_rsa->validateSign($signed_bytes, $signature, null, $hash); - if ($this->_rsa->isError()) { - $error = $this->keypair->getLastError(); - common_log(LOG_DEBUG, 'RSA Error: '. $error->getMessage()); - return false; - } - return $result; + $signature = base64_url_decode($signature); + return $this->publicKey->verify($signed_bytes, $signature); } } -// Define a sha256 function for hashing -// (Crypt_RSA should really be updated to use hash() ) -function magicsig_sha256($bytes) -{ - return hash('sha256', $bytes); -} - function base64_url_encode($input) { return strtr(base64_encode($input), '+/', '-_'); -- cgit v1.2.3-54-g00ecf From c5bb41176ee246369e05b932f031f40e38087f23 Mon Sep 17 00:00:00 2001 From: James Walker Date: Fri, 12 Mar 2010 19:42:48 -0500 Subject: converted toString to new crypt library --- plugins/OStatus/classes/Magicsig.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/classes/Magicsig.php b/plugins/OStatus/classes/Magicsig.php index d25b150e8..d1d6a6d45 100644 --- a/plugins/OStatus/classes/Magicsig.php +++ b/plugins/OStatus/classes/Magicsig.php @@ -110,14 +110,11 @@ class Magicsig extends Memcached_DataObject public function toString($full_pair = true) { - $public_key = $this->_rsa->_public_key; - $private_key = $this->_rsa->_private_key; - - $mod = base64_url_encode($public_key->getModulus()); - $exp = base64_url_encode($public_key->getExponent()); + $mod = base64_url_encode($this->publicKey->modulus->toBytes()); + $exp = base64_url_encode($this->publicKey->exponent->toBytes()); $private_exp = ''; if ($full_pair && $private_key->getExponent()) { - $private_exp = '.' . base64_url_encode($private_key->getExponent()); + $private_exp = '.' . base64_url_encode($this->privateKey->exponent->toBytes()); } return 'RSA.' . $mod . '.' . $exp . $private_exp; -- cgit v1.2.3-54-g00ecf From 520faaf67d7bd7bb0a87322d3f2e244c22d0c994 Mon Sep 17 00:00:00 2001 From: James Walker Date: Fri, 12 Mar 2010 20:01:34 -0500 Subject: updating phpseclib to latest cvs - fixes a bunch of key generation issues --- plugins/OStatus/extlib/Crypt/AES.php | 898 ++-- plugins/OStatus/extlib/Crypt/DES.php | 1794 ++++---- plugins/OStatus/extlib/Crypt/Hash.php | 1630 +++---- plugins/OStatus/extlib/Crypt/RC4.php | 984 ++--- plugins/OStatus/extlib/Crypt/RSA.php | 4035 +++++++++-------- plugins/OStatus/extlib/Crypt/Random.php | 193 +- plugins/OStatus/extlib/Crypt/Rijndael.php | 2375 +++++----- plugins/OStatus/extlib/Crypt/TripleDES.php | 1291 +++--- plugins/OStatus/extlib/Math/BigInteger.php | 6603 +++++++++++++++------------- 9 files changed, 10434 insertions(+), 9369 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/extlib/Crypt/AES.php b/plugins/OStatus/extlib/Crypt/AES.php index 4b062c4f2..68ab4db09 100644 --- a/plugins/OStatus/extlib/Crypt/AES.php +++ b/plugins/OStatus/extlib/Crypt/AES.php @@ -1,421 +1,479 @@ - - * setKey('abcdefghijklmnop'); - * - * $size = 10 * 1024; - * $plaintext = ''; - * for ($i = 0; $i < $size; $i++) { - * $plaintext.= 'a'; - * } - * - * echo $aes->decrypt($aes->encrypt($plaintext)); - * ?> - * - * - * LICENSE: This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, - * MA 02111-1307 USA - * - * @category Crypt - * @package Crypt_AES - * @author Jim Wigginton - * @copyright MMVIII Jim Wigginton - * @license http://www.gnu.org/licenses/lgpl.txt - * @version $Id: AES.php,v 1.5 2009/11/23 19:06:06 terrafrost Exp $ - * @link http://phpseclib.sourceforge.net - */ - -/** - * Include Crypt_Rijndael - */ -require_once 'Rijndael.php'; - -/**#@+ - * @access public - * @see Crypt_AES::encrypt() - * @see Crypt_AES::decrypt() - */ -/** - * Encrypt / decrypt using the Electronic Code Book mode. - * - * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29 - */ -define('CRYPT_AES_MODE_ECB', 1); -/** - * Encrypt / decrypt using the Code Book Chaining mode. - * - * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29 - */ -define('CRYPT_AES_MODE_CBC', 2); -/**#@-*/ - -/**#@+ - * @access private - * @see Crypt_AES::Crypt_AES() - */ -/** - * Toggles the internal implementation - */ -define('CRYPT_AES_MODE_INTERNAL', 1); -/** - * Toggles the mcrypt implementation - */ -define('CRYPT_AES_MODE_MCRYPT', 2); -/**#@-*/ - -/** - * Pure-PHP implementation of AES. - * - * @author Jim Wigginton - * @version 0.1.0 - * @access public - * @package Crypt_AES - */ -class Crypt_AES extends Crypt_Rijndael { - /** - * MCrypt parameters - * - * @see Crypt_AES::setMCrypt() - * @var Array - * @access private - */ - var $mcrypt = array('', ''); - - /** - * Default Constructor. - * - * Determines whether or not the mcrypt extension should be used. $mode should only, at present, be - * CRYPT_AES_MODE_ECB or CRYPT_AES_MODE_CBC. If not explictly set, CRYPT_AES_MODE_CBC will be used. - * - * @param optional Integer $mode - * @return Crypt_AES - * @access public - */ - function Crypt_AES($mode = CRYPT_AES_MODE_CBC) - { - if ( !defined('CRYPT_AES_MODE') ) { - switch (true) { - case extension_loaded('mcrypt'): - // i'd check to see if aes was supported, by doing in_array('des', mcrypt_list_algorithms('')), - // but since that can be changed after the object has been created, there doesn't seem to be - // a lot of point... - define('CRYPT_AES_MODE', CRYPT_AES_MODE_MCRYPT); - break; - default: - define('CRYPT_AES_MODE', CRYPT_AES_MODE_INTERNAL); - } - } - - switch ( CRYPT_AES_MODE ) { - case CRYPT_AES_MODE_MCRYPT: - switch ($mode) { - case CRYPT_AES_MODE_ECB: - $this->mode = MCRYPT_MODE_ECB; - break; - case CRYPT_AES_MODE_CBC: - default: - $this->mode = MCRYPT_MODE_CBC; - } - - break; - default: - switch ($mode) { - case CRYPT_AES_MODE_ECB: - $this->mode = CRYPT_RIJNDAEL_MODE_ECB; - break; - case CRYPT_AES_MODE_CBC: - default: - $this->mode = CRYPT_RIJNDAEL_MODE_CBC; - } - } - - if (CRYPT_AES_MODE == CRYPT_AES_MODE_INTERNAL) { - parent::Crypt_Rijndael($this->mode); - } - } - - /** - * Dummy function - * - * Since Crypt_AES extends Crypt_Rijndael, this function is, technically, available, but it doesn't do anything. - * - * @access public - * @param Integer $length - */ - function setBlockLength($length) - { - return; - } - - /** - * Encrypts a message. - * - * $plaintext will be padded with up to 16 additional bytes. Other AES implementations may or may not pad in the - * same manner. Other common approaches to padding and the reasons why it's necessary are discussed in the following - * URL: - * - * {@link http://www.di-mgt.com.au/cryptopad.html http://www.di-mgt.com.au/cryptopad.html} - * - * An alternative to padding is to, separately, send the length of the file. This is what SSH, in fact, does. - * strlen($plaintext) will still need to be a multiple of 16, however, arbitrary values can be added to make it that - * length. - * - * @see Crypt_AES::decrypt() - * @access public - * @param String $plaintext - */ - function encrypt($plaintext) - { - if ( CRYPT_AES_MODE == CRYPT_AES_MODE_MCRYPT ) { - $this->_mcryptSetup(); - $plaintext = $this->_pad($plaintext); - - $td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, $this->mcrypt[0], $this->mode, $this->mcrypt[1]); - mcrypt_generic_init($td, $this->key, $this->encryptIV); - - $ciphertext = mcrypt_generic($td, $plaintext); - - mcrypt_generic_deinit($td); - mcrypt_module_close($td); - - if ($this->continuousBuffer) { - $this->encryptIV = substr($ciphertext, -16); - } - - return $ciphertext; - } - - return parent::encrypt($plaintext); - } - - /** - * Decrypts a message. - * - * If strlen($ciphertext) is not a multiple of 16, null bytes will be added to the end of the string until it is. - * - * @see Crypt_AES::encrypt() - * @access public - * @param String $ciphertext - */ - function decrypt($ciphertext) - { - // we pad with chr(0) since that's what mcrypt_generic does. to quote from http://php.net/function.mcrypt-generic : - // "The data is padded with "\0" to make sure the length of the data is n * blocksize." - $ciphertext = str_pad($ciphertext, (strlen($ciphertext) + 15) & 0xFFFFFFF0, chr(0)); - - if ( CRYPT_AES_MODE == CRYPT_AES_MODE_MCRYPT ) { - $this->_mcryptSetup(); - - $td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, $this->mcrypt[0], $this->mode, $this->mcrypt[1]); - mcrypt_generic_init($td, $this->key, $this->decryptIV); - - $plaintext = mdecrypt_generic($td, $ciphertext); - - mcrypt_generic_deinit($td); - mcrypt_module_close($td); - - if ($this->continuousBuffer) { - $this->decryptIV = substr($ciphertext, -16); - } - - return $this->_unpad($plaintext); - } - - return parent::decrypt($ciphertext); - } - - /** - * Sets MCrypt parameters. (optional) - * - * If MCrypt is being used, empty strings will be used, unless otherwise specified. - * - * @link http://php.net/function.mcrypt-module-open#function.mcrypt-module-open - * @access public - * @param optional Integer $algorithm_directory - * @param optional Integer $mode_directory - */ - function setMCrypt($algorithm_directory = '', $mode_directory = '') - { - $this->mcrypt = array($algorithm_directory, $mode_directory); - } - - /** - * Setup mcrypt - * - * Validates all the variables. - * - * @access private - */ - function _mcryptSetup() - { - if (!$this->changed) { - return; - } - - if (!$this->explicit_key_length) { - // this just copied from Crypt_Rijndael::_setup() - $length = strlen($this->key) >> 2; - if ($length > 8) { - $length = 8; - } else if ($length < 4) { - $length = 4; - } - $this->Nk = $length; - $this->key_size = $length << 2; - } - - switch ($this->Nk) { - case 4: // 128 - $this->key_size = 16; - break; - case 5: // 160 - case 6: // 192 - $this->key_size = 24; - break; - case 7: // 224 - case 8: // 256 - $this->key_size = 32; - } - - $this->key = substr($this->key, 0, $this->key_size); - $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($this->iv, 0, 16), 16, chr(0)); - - $this->changed = false; - } - - /** - * Encrypts a block - * - * Optimized over Crypt_Rijndael's implementation by means of loop unrolling. - * - * @see Crypt_Rijndael::_encryptBlock() - * @access private - * @param String $in - * @return String - */ - function _encryptBlock($in) - { - $state = unpack('N*word', $in); - - // addRoundKey and reindex $state - $state = array( - $state['word1'] ^ $this->w[0][0], - $state['word2'] ^ $this->w[0][1], - $state['word3'] ^ $this->w[0][2], - $state['word4'] ^ $this->w[0][3] - ); - - // shiftRows + subWord + mixColumns + addRoundKey - // we could loop unroll this and use if statements to do more rounds as necessary, but, in my tests, that yields - // only a marginal improvement. since that also, imho, hinders the readability of the code, i've opted not to do it. - for ($round = 1; $round < $this->Nr; $round++) { - $state = array( - $this->t0[$state[0] & 0xFF000000] ^ $this->t1[$state[1] & 0x00FF0000] ^ $this->t2[$state[2] & 0x0000FF00] ^ $this->t3[$state[3] & 0x000000FF] ^ $this->w[$round][0], - $this->t0[$state[1] & 0xFF000000] ^ $this->t1[$state[2] & 0x00FF0000] ^ $this->t2[$state[3] & 0x0000FF00] ^ $this->t3[$state[0] & 0x000000FF] ^ $this->w[$round][1], - $this->t0[$state[2] & 0xFF000000] ^ $this->t1[$state[3] & 0x00FF0000] ^ $this->t2[$state[0] & 0x0000FF00] ^ $this->t3[$state[1] & 0x000000FF] ^ $this->w[$round][2], - $this->t0[$state[3] & 0xFF000000] ^ $this->t1[$state[0] & 0x00FF0000] ^ $this->t2[$state[1] & 0x0000FF00] ^ $this->t3[$state[2] & 0x000000FF] ^ $this->w[$round][3] - ); - - } - - // subWord - $state = array( - $this->_subWord($state[0]), - $this->_subWord($state[1]), - $this->_subWord($state[2]), - $this->_subWord($state[3]) - ); - - // shiftRows + addRoundKey - $state = array( - ($state[0] & 0xFF000000) ^ ($state[1] & 0x00FF0000) ^ ($state[2] & 0x0000FF00) ^ ($state[3] & 0x000000FF) ^ $this->w[$this->Nr][0], - ($state[1] & 0xFF000000) ^ ($state[2] & 0x00FF0000) ^ ($state[3] & 0x0000FF00) ^ ($state[0] & 0x000000FF) ^ $this->w[$this->Nr][1], - ($state[2] & 0xFF000000) ^ ($state[3] & 0x00FF0000) ^ ($state[0] & 0x0000FF00) ^ ($state[1] & 0x000000FF) ^ $this->w[$this->Nr][2], - ($state[3] & 0xFF000000) ^ ($state[0] & 0x00FF0000) ^ ($state[1] & 0x0000FF00) ^ ($state[2] & 0x000000FF) ^ $this->w[$this->Nr][3] - ); - - return pack('N*', $state[0], $state[1], $state[2], $state[3]); - } - - /** - * Decrypts a block - * - * Optimized over Crypt_Rijndael's implementation by means of loop unrolling. - * - * @see Crypt_Rijndael::_decryptBlock() - * @access private - * @param String $in - * @return String - */ - function _decryptBlock($in) - { - $state = unpack('N*word', $in); - - // addRoundKey and reindex $state - $state = array( - $state['word1'] ^ $this->dw[$this->Nr][0], - $state['word2'] ^ $this->dw[$this->Nr][1], - $state['word3'] ^ $this->dw[$this->Nr][2], - $state['word4'] ^ $this->dw[$this->Nr][3] - ); - - - // invShiftRows + invSubBytes + invMixColumns + addRoundKey - for ($round = $this->Nr - 1; $round > 0; $round--) { - $state = array( - $this->dt0[$state[0] & 0xFF000000] ^ $this->dt1[$state[3] & 0x00FF0000] ^ $this->dt2[$state[2] & 0x0000FF00] ^ $this->dt3[$state[1] & 0x000000FF] ^ $this->dw[$round][0], - $this->dt0[$state[1] & 0xFF000000] ^ $this->dt1[$state[0] & 0x00FF0000] ^ $this->dt2[$state[3] & 0x0000FF00] ^ $this->dt3[$state[2] & 0x000000FF] ^ $this->dw[$round][1], - $this->dt0[$state[2] & 0xFF000000] ^ $this->dt1[$state[1] & 0x00FF0000] ^ $this->dt2[$state[0] & 0x0000FF00] ^ $this->dt3[$state[3] & 0x000000FF] ^ $this->dw[$round][2], - $this->dt0[$state[3] & 0xFF000000] ^ $this->dt1[$state[2] & 0x00FF0000] ^ $this->dt2[$state[1] & 0x0000FF00] ^ $this->dt3[$state[0] & 0x000000FF] ^ $this->dw[$round][3] - ); - } - - // invShiftRows + invSubWord + addRoundKey - $state = array( - $this->_invSubWord(($state[0] & 0xFF000000) ^ ($state[3] & 0x00FF0000) ^ ($state[2] & 0x0000FF00) ^ ($state[1] & 0x000000FF)) ^ $this->dw[0][0], - $this->_invSubWord(($state[1] & 0xFF000000) ^ ($state[0] & 0x00FF0000) ^ ($state[3] & 0x0000FF00) ^ ($state[2] & 0x000000FF)) ^ $this->dw[0][1], - $this->_invSubWord(($state[2] & 0xFF000000) ^ ($state[1] & 0x00FF0000) ^ ($state[0] & 0x0000FF00) ^ ($state[3] & 0x000000FF)) ^ $this->dw[0][2], - $this->_invSubWord(($state[3] & 0xFF000000) ^ ($state[2] & 0x00FF0000) ^ ($state[1] & 0x0000FF00) ^ ($state[0] & 0x000000FF)) ^ $this->dw[0][3] - ); - - return pack('N*', $state[0], $state[1], $state[2], $state[3]); - } -} - -// vim: ts=4:sw=4:et: + + * setKey('abcdefghijklmnop'); + * + * $size = 10 * 1024; + * $plaintext = ''; + * for ($i = 0; $i < $size; $i++) { + * $plaintext.= 'a'; + * } + * + * echo $aes->decrypt($aes->encrypt($plaintext)); + * ?> + * + * + * LICENSE: This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * @category Crypt + * @package Crypt_AES + * @author Jim Wigginton + * @copyright MMVIII Jim Wigginton + * @license http://www.gnu.org/licenses/lgpl.txt + * @version $Id: AES.php,v 1.7 2010/02/09 06:10:25 terrafrost Exp $ + * @link http://phpseclib.sourceforge.net + */ + +/** + * Include Crypt_Rijndael + */ +require_once 'Rijndael.php'; + +/**#@+ + * @access public + * @see Crypt_AES::encrypt() + * @see Crypt_AES::decrypt() + */ +/** + * Encrypt / decrypt using the Counter mode. + * + * Set to -1 since that's what Crypt/Random.php uses to index the CTR mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Counter_.28CTR.29 + */ +define('CRYPT_AES_MODE_CTR', -1); +/** + * Encrypt / decrypt using the Electronic Code Book mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29 + */ +define('CRYPT_AES_MODE_ECB', 1); +/** + * Encrypt / decrypt using the Code Book Chaining mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29 + */ +define('CRYPT_AES_MODE_CBC', 2); +/**#@-*/ + +/**#@+ + * @access private + * @see Crypt_AES::Crypt_AES() + */ +/** + * Toggles the internal implementation + */ +define('CRYPT_AES_MODE_INTERNAL', 1); +/** + * Toggles the mcrypt implementation + */ +define('CRYPT_AES_MODE_MCRYPT', 2); +/**#@-*/ + +/** + * Pure-PHP implementation of AES. + * + * @author Jim Wigginton + * @version 0.1.0 + * @access public + * @package Crypt_AES + */ +class Crypt_AES extends Crypt_Rijndael { + /** + * mcrypt resource for encryption + * + * The mcrypt resource can be recreated every time something needs to be created or it can be created just once. + * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode. + * + * @see Crypt_AES::encrypt() + * @var String + * @access private + */ + var $enmcrypt; + + /** + * mcrypt resource for decryption + * + * The mcrypt resource can be recreated every time something needs to be created or it can be created just once. + * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode. + * + * @see Crypt_AES::decrypt() + * @var String + * @access private + */ + var $demcrypt; + + /** + * Default Constructor. + * + * Determines whether or not the mcrypt extension should be used. $mode should only, at present, be + * CRYPT_AES_MODE_ECB or CRYPT_AES_MODE_CBC. If not explictly set, CRYPT_AES_MODE_CBC will be used. + * + * @param optional Integer $mode + * @return Crypt_AES + * @access public + */ + function Crypt_AES($mode = CRYPT_AES_MODE_CBC) + { + if ( !defined('CRYPT_AES_MODE') ) { + switch (true) { + case extension_loaded('mcrypt'): + // i'd check to see if aes was supported, by doing in_array('des', mcrypt_list_algorithms('')), + // but since that can be changed after the object has been created, there doesn't seem to be + // a lot of point... + define('CRYPT_AES_MODE', CRYPT_AES_MODE_MCRYPT); + break; + default: + define('CRYPT_AES_MODE', CRYPT_AES_MODE_INTERNAL); + } + } + + switch ( CRYPT_AES_MODE ) { + case CRYPT_AES_MODE_MCRYPT: + switch ($mode) { + case CRYPT_AES_MODE_ECB: + $this->mode = MCRYPT_MODE_ECB; + break; + case CRYPT_AES_MODE_CTR: + // ctr doesn't have a constant associated with it even though it appears to be fairly widely + // supported. in lieu of knowing just how widely supported it is, i've, for now, opted not to + // include a compatibility layer. the layer has been implemented but, for now, is commented out. + $this->mode = 'ctr'; + //$this->mode = in_array('ctr', mcrypt_list_modes()) ? 'ctr' : CRYPT_AES_MODE_CTR; + break; + case CRYPT_AES_MODE_CBC: + default: + $this->mode = MCRYPT_MODE_CBC; + } + + break; + default: + switch ($mode) { + case CRYPT_AES_MODE_ECB: + $this->mode = CRYPT_RIJNDAEL_MODE_ECB; + break; + case CRYPT_AES_MODE_CTR: + $this->mode = CRYPT_RIJNDAEL_MODE_CTR; + break; + case CRYPT_AES_MODE_CBC: + default: + $this->mode = CRYPT_RIJNDAEL_MODE_CBC; + } + } + + if (CRYPT_AES_MODE == CRYPT_AES_MODE_INTERNAL) { + parent::Crypt_Rijndael($this->mode); + } + } + + /** + * Dummy function + * + * Since Crypt_AES extends Crypt_Rijndael, this function is, technically, available, but it doesn't do anything. + * + * @access public + * @param Integer $length + */ + function setBlockLength($length) + { + return; + } + + /** + * Encrypts a message. + * + * $plaintext will be padded with up to 16 additional bytes. Other AES implementations may or may not pad in the + * same manner. Other common approaches to padding and the reasons why it's necessary are discussed in the following + * URL: + * + * {@link http://www.di-mgt.com.au/cryptopad.html http://www.di-mgt.com.au/cryptopad.html} + * + * An alternative to padding is to, separately, send the length of the file. This is what SSH, in fact, does. + * strlen($plaintext) will still need to be a multiple of 16, however, arbitrary values can be added to make it that + * length. + * + * @see Crypt_AES::decrypt() + * @access public + * @param String $plaintext + */ + function encrypt($plaintext) + { + if ( CRYPT_AES_MODE == CRYPT_AES_MODE_MCRYPT ) { + $this->_mcryptSetup(); + /* + if ($this->mode == CRYPT_AES_MODE_CTR) { + $iv = $this->encryptIV; + $xor = mcrypt_generic($this->enmcrypt, $this->_generate_xor(strlen($plaintext), $iv)); + $ciphertext = $plaintext ^ $xor; + if ($this->continuousBuffer) { + $this->encryptIV = $iv; + } + return $ciphertext; + } + */ + + if ($this->mode != 'ctr') { + $plaintext = $this->_pad($plaintext); + } + + $ciphertext = mcrypt_generic($this->enmcrypt, $plaintext); + + if (!$this->continuousBuffer) { + mcrypt_generic_init($this->enmcrypt, $this->key, $this->iv); + } + + return $ciphertext; + } + + return parent::encrypt($plaintext); + } + + /** + * Decrypts a message. + * + * If strlen($ciphertext) is not a multiple of 16, null bytes will be added to the end of the string until it is. + * + * @see Crypt_AES::encrypt() + * @access public + * @param String $ciphertext + */ + function decrypt($ciphertext) + { + if ( CRYPT_AES_MODE == CRYPT_AES_MODE_MCRYPT ) { + $this->_mcryptSetup(); + /* + if ($this->mode == CRYPT_AES_MODE_CTR) { + $iv = $this->decryptIV; + $xor = mcrypt_generic($this->enmcrypt, $this->_generate_xor(strlen($ciphertext), $iv)); + $plaintext = $ciphertext ^ $xor; + if ($this->continuousBuffer) { + $this->decryptIV = $iv; + } + return $plaintext; + } + */ + + if ($this->mode != 'ctr') { + // we pad with chr(0) since that's what mcrypt_generic does. to quote from http://php.net/function.mcrypt-generic : + // "The data is padded with "\0" to make sure the length of the data is n * blocksize." + $ciphertext = str_pad($ciphertext, (strlen($ciphertext) + 15) & 0xFFFFFFF0, chr(0)); + } + + $plaintext = mdecrypt_generic($this->demcrypt, $ciphertext); + + if (!$this->continuousBuffer) { + mcrypt_generic_init($this->demcrypt, $this->key, $this->iv); + } + + return $this->mode != 'ctr' ? $this->_unpad($plaintext) : $plaintext; + } + + return parent::decrypt($ciphertext); + } + + /** + * Setup mcrypt + * + * Validates all the variables. + * + * @access private + */ + function _mcryptSetup() + { + if (!$this->changed) { + return; + } + + if (!$this->explicit_key_length) { + // this just copied from Crypt_Rijndael::_setup() + $length = strlen($this->key) >> 2; + if ($length > 8) { + $length = 8; + } else if ($length < 4) { + $length = 4; + } + $this->Nk = $length; + $this->key_size = $length << 2; + } + + switch ($this->Nk) { + case 4: // 128 + $this->key_size = 16; + break; + case 5: // 160 + case 6: // 192 + $this->key_size = 24; + break; + case 7: // 224 + case 8: // 256 + $this->key_size = 32; + } + + $this->key = substr($this->key, 0, $this->key_size); + $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($this->iv, 0, 16), 16, chr(0)); + + if (!isset($this->enmcrypt)) { + $mode = $this->mode; + //$mode = $this->mode == CRYPT_AES_MODE_CTR ? MCRYPT_MODE_ECB : $this->mode; + + $this->demcrypt = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', $mode, ''); + $this->enmcrypt = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', $mode, ''); + } // else should mcrypt_generic_deinit be called? + + mcrypt_generic_init($this->demcrypt, $this->key, $this->iv); + mcrypt_generic_init($this->enmcrypt, $this->key, $this->iv); + + $this->changed = false; + } + + /** + * Encrypts a block + * + * Optimized over Crypt_Rijndael's implementation by means of loop unrolling. + * + * @see Crypt_Rijndael::_encryptBlock() + * @access private + * @param String $in + * @return String + */ + function _encryptBlock($in) + { + $state = unpack('N*word', $in); + + $Nr = $this->Nr; + $w = $this->w; + $t0 = $this->t0; + $t1 = $this->t1; + $t2 = $this->t2; + $t3 = $this->t3; + + // addRoundKey and reindex $state + $state = array( + $state['word1'] ^ $w[0][0], + $state['word2'] ^ $w[0][1], + $state['word3'] ^ $w[0][2], + $state['word4'] ^ $w[0][3] + ); + + // shiftRows + subWord + mixColumns + addRoundKey + // we could loop unroll this and use if statements to do more rounds as necessary, but, in my tests, that yields + // only a marginal improvement. since that also, imho, hinders the readability of the code, i've opted not to do it. + for ($round = 1; $round < $this->Nr; $round++) { + $state = array( + $t0[$state[0] & 0xFF000000] ^ $t1[$state[1] & 0x00FF0000] ^ $t2[$state[2] & 0x0000FF00] ^ $t3[$state[3] & 0x000000FF] ^ $w[$round][0], + $t0[$state[1] & 0xFF000000] ^ $t1[$state[2] & 0x00FF0000] ^ $t2[$state[3] & 0x0000FF00] ^ $t3[$state[0] & 0x000000FF] ^ $w[$round][1], + $t0[$state[2] & 0xFF000000] ^ $t1[$state[3] & 0x00FF0000] ^ $t2[$state[0] & 0x0000FF00] ^ $t3[$state[1] & 0x000000FF] ^ $w[$round][2], + $t0[$state[3] & 0xFF000000] ^ $t1[$state[0] & 0x00FF0000] ^ $t2[$state[1] & 0x0000FF00] ^ $t3[$state[2] & 0x000000FF] ^ $w[$round][3] + ); + + } + + // subWord + $state = array( + $this->_subWord($state[0]), + $this->_subWord($state[1]), + $this->_subWord($state[2]), + $this->_subWord($state[3]) + ); + + // shiftRows + addRoundKey + $state = array( + ($state[0] & 0xFF000000) ^ ($state[1] & 0x00FF0000) ^ ($state[2] & 0x0000FF00) ^ ($state[3] & 0x000000FF) ^ $this->w[$this->Nr][0], + ($state[1] & 0xFF000000) ^ ($state[2] & 0x00FF0000) ^ ($state[3] & 0x0000FF00) ^ ($state[0] & 0x000000FF) ^ $this->w[$this->Nr][1], + ($state[2] & 0xFF000000) ^ ($state[3] & 0x00FF0000) ^ ($state[0] & 0x0000FF00) ^ ($state[1] & 0x000000FF) ^ $this->w[$this->Nr][2], + ($state[3] & 0xFF000000) ^ ($state[0] & 0x00FF0000) ^ ($state[1] & 0x0000FF00) ^ ($state[2] & 0x000000FF) ^ $this->w[$this->Nr][3] + ); + + return pack('N*', $state[0], $state[1], $state[2], $state[3]); + } + + /** + * Decrypts a block + * + * Optimized over Crypt_Rijndael's implementation by means of loop unrolling. + * + * @see Crypt_Rijndael::_decryptBlock() + * @access private + * @param String $in + * @return String + */ + function _decryptBlock($in) + { + $state = unpack('N*word', $in); + + $Nr = $this->Nr; + $dw = $this->dw; + $dt0 = $this->dt0; + $dt1 = $this->dt1; + $dt2 = $this->dt2; + $dt3 = $this->dt3; + + // addRoundKey and reindex $state + $state = array( + $state['word1'] ^ $dw[$this->Nr][0], + $state['word2'] ^ $dw[$this->Nr][1], + $state['word3'] ^ $dw[$this->Nr][2], + $state['word4'] ^ $dw[$this->Nr][3] + ); + + + // invShiftRows + invSubBytes + invMixColumns + addRoundKey + for ($round = $this->Nr - 1; $round > 0; $round--) { + $state = array( + $dt0[$state[0] & 0xFF000000] ^ $dt1[$state[3] & 0x00FF0000] ^ $dt2[$state[2] & 0x0000FF00] ^ $dt3[$state[1] & 0x000000FF] ^ $dw[$round][0], + $dt0[$state[1] & 0xFF000000] ^ $dt1[$state[0] & 0x00FF0000] ^ $dt2[$state[3] & 0x0000FF00] ^ $dt3[$state[2] & 0x000000FF] ^ $dw[$round][1], + $dt0[$state[2] & 0xFF000000] ^ $dt1[$state[1] & 0x00FF0000] ^ $dt2[$state[0] & 0x0000FF00] ^ $dt3[$state[3] & 0x000000FF] ^ $dw[$round][2], + $dt0[$state[3] & 0xFF000000] ^ $dt1[$state[2] & 0x00FF0000] ^ $dt2[$state[1] & 0x0000FF00] ^ $dt3[$state[0] & 0x000000FF] ^ $dw[$round][3] + ); + } + + // invShiftRows + invSubWord + addRoundKey + $state = array( + $this->_invSubWord(($state[0] & 0xFF000000) ^ ($state[3] & 0x00FF0000) ^ ($state[2] & 0x0000FF00) ^ ($state[1] & 0x000000FF)) ^ $dw[0][0], + $this->_invSubWord(($state[1] & 0xFF000000) ^ ($state[0] & 0x00FF0000) ^ ($state[3] & 0x0000FF00) ^ ($state[2] & 0x000000FF)) ^ $dw[0][1], + $this->_invSubWord(($state[2] & 0xFF000000) ^ ($state[1] & 0x00FF0000) ^ ($state[0] & 0x0000FF00) ^ ($state[3] & 0x000000FF)) ^ $dw[0][2], + $this->_invSubWord(($state[3] & 0xFF000000) ^ ($state[2] & 0x00FF0000) ^ ($state[1] & 0x0000FF00) ^ ($state[0] & 0x000000FF)) ^ $dw[0][3] + ); + + return pack('N*', $state[0], $state[1], $state[2], $state[3]); + } +} + +// vim: ts=4:sw=4:et: // vim6: fdl=1: \ No newline at end of file diff --git a/plugins/OStatus/extlib/Crypt/DES.php b/plugins/OStatus/extlib/Crypt/DES.php index 3fd0b65ec..985ed25b5 100644 --- a/plugins/OStatus/extlib/Crypt/DES.php +++ b/plugins/OStatus/extlib/Crypt/DES.php @@ -1,851 +1,945 @@ - - * setKey('abcdefgh'); - * - * $size = 10 * 1024; - * $plaintext = ''; - * for ($i = 0; $i < $size; $i++) { - * $plaintext.= 'a'; - * } - * - * echo $des->decrypt($des->encrypt($plaintext)); - * ?> - * - * - * LICENSE: This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, - * MA 02111-1307 USA - * - * @category Crypt - * @package Crypt_DES - * @author Jim Wigginton - * @copyright MMVII Jim Wigginton - * @license http://www.gnu.org/licenses/lgpl.txt - * @version $Id: DES.php,v 1.9 2009/11/23 19:06:06 terrafrost Exp $ - * @link http://phpseclib.sourceforge.net - */ - -/**#@+ - * @access private - * @see Crypt_DES::_prepareKey() - * @see Crypt_DES::_processBlock() - */ -/** - * Contains array_reverse($keys[CRYPT_DES_DECRYPT]) - */ -define('CRYPT_DES_ENCRYPT', 0); -/** - * Contains array_reverse($keys[CRYPT_DES_ENCRYPT]) - */ -define('CRYPT_DES_DECRYPT', 1); -/**#@-*/ - -/**#@+ - * @access public - * @see Crypt_DES::encrypt() - * @see Crypt_DES::decrypt() - */ -/** - * Encrypt / decrypt using the Electronic Code Book mode. - * - * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29 - */ -define('CRYPT_DES_MODE_ECB', 1); -/** - * Encrypt / decrypt using the Code Book Chaining mode. - * - * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29 - */ -define('CRYPT_DES_MODE_CBC', 2); -/**#@-*/ - -/**#@+ - * @access private - * @see Crypt_DES::Crypt_DES() - */ -/** - * Toggles the internal implementation - */ -define('CRYPT_DES_MODE_INTERNAL', 1); -/** - * Toggles the mcrypt implementation - */ -define('CRYPT_DES_MODE_MCRYPT', 2); -/**#@-*/ - -/** - * Pure-PHP implementation of DES. - * - * @author Jim Wigginton - * @version 0.1.0 - * @access public - * @package Crypt_DES - */ -class Crypt_DES { - /** - * The Key Schedule - * - * @see Crypt_DES::setKey() - * @var Array - * @access private - */ - var $keys = "\0\0\0\0\0\0\0\0"; - - /** - * The Encryption Mode - * - * @see Crypt_DES::Crypt_DES() - * @var Integer - * @access private - */ - var $mode; - - /** - * Continuous Buffer status - * - * @see Crypt_DES::enableContinuousBuffer() - * @var Boolean - * @access private - */ - var $continuousBuffer = false; - - /** - * Padding status - * - * @see Crypt_DES::enablePadding() - * @var Boolean - * @access private - */ - var $padding = true; - - /** - * The Initialization Vector - * - * @see Crypt_DES::setIV() - * @var String - * @access private - */ - var $iv = "\0\0\0\0\0\0\0\0"; - - /** - * A "sliding" Initialization Vector - * - * @see Crypt_DES::enableContinuousBuffer() - * @var String - * @access private - */ - var $encryptIV = "\0\0\0\0\0\0\0\0"; - - /** - * A "sliding" Initialization Vector - * - * @see Crypt_DES::enableContinuousBuffer() - * @var String - * @access private - */ - var $decryptIV = "\0\0\0\0\0\0\0\0"; - - /** - * MCrypt parameters - * - * @see Crypt_DES::setMCrypt() - * @var Array - * @access private - */ - var $mcrypt = array('', ''); - - /** - * Default Constructor. - * - * Determines whether or not the mcrypt extension should be used. $mode should only, at present, be - * CRYPT_DES_MODE_ECB or CRYPT_DES_MODE_CBC. If not explictly set, CRYPT_DES_MODE_CBC will be used. - * - * @param optional Integer $mode - * @return Crypt_DES - * @access public - */ - function Crypt_DES($mode = CRYPT_MODE_DES_CBC) - { - if ( !defined('CRYPT_DES_MODE') ) { - switch (true) { - case extension_loaded('mcrypt'): - // i'd check to see if des was supported, by doing in_array('des', mcrypt_list_algorithms('')), - // but since that can be changed after the object has been created, there doesn't seem to be - // a lot of point... - define('CRYPT_DES_MODE', CRYPT_DES_MODE_MCRYPT); - break; - default: - define('CRYPT_DES_MODE', CRYPT_DES_MODE_INTERNAL); - } - } - - switch ( CRYPT_DES_MODE ) { - case CRYPT_DES_MODE_MCRYPT: - switch ($mode) { - case CRYPT_DES_MODE_ECB: - $this->mode = MCRYPT_MODE_ECB; - break; - case CRYPT_DES_MODE_CBC: - default: - $this->mode = MCRYPT_MODE_CBC; - } - - break; - default: - switch ($mode) { - case CRYPT_DES_MODE_ECB: - case CRYPT_DES_MODE_CBC: - $this->mode = $mode; - break; - default: - $this->mode = CRYPT_DES_MODE_CBC; - } - } - } - - /** - * Sets the key. - * - * Keys can be of any length. DES, itself, uses 64-bit keys (eg. strlen($key) == 8), however, we - * only use the first eight, if $key has more then eight characters in it, and pad $key with the - * null byte if it is less then eight characters long. - * - * DES also requires that every eighth bit be a parity bit, however, we'll ignore that. - * - * If the key is not explicitly set, it'll be assumed to be all zero's. - * - * @access public - * @param String $key - */ - function setKey($key) - { - $this->keys = ( CRYPT_DES_MODE == CRYPT_DES_MODE_MCRYPT ) ? substr($key, 0, 8) : $this->_prepareKey($key); - } - - /** - * Sets the initialization vector. (optional) - * - * SetIV is not required when CRYPT_DES_MODE_ECB is being used. If not explictly set, it'll be assumed - * to be all zero's. - * - * @access public - * @param String $iv - */ - function setIV($iv) - { - $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($iv, 0, 8), 8, chr(0));; - } - - /** - * Sets MCrypt parameters. (optional) - * - * If MCrypt is being used, empty strings will be used, unless otherwise specified. - * - * @link http://php.net/function.mcrypt-module-open#function.mcrypt-module-open - * @access public - * @param optional Integer $algorithm_directory - * @param optional Integer $mode_directory - */ - function setMCrypt($algorithm_directory = '', $mode_directory = '') - { - $this->mcrypt = array($algorithm_directory, $mode_directory); - } - - /** - * Encrypts a message. - * - * $plaintext will be padded with up to 8 additional bytes. Other DES implementations may or may not pad in the - * same manner. Other common approaches to padding and the reasons why it's necessary are discussed in the following - * URL: - * - * {@link http://www.di-mgt.com.au/cryptopad.html http://www.di-mgt.com.au/cryptopad.html} - * - * An alternative to padding is to, separately, send the length of the file. This is what SSH, in fact, does. - * strlen($plaintext) will still need to be a multiple of 8, however, arbitrary values can be added to make it that - * length. - * - * @see Crypt_DES::decrypt() - * @access public - * @param String $plaintext - */ - function encrypt($plaintext) - { - $plaintext = $this->_pad($plaintext); - - if ( CRYPT_DES_MODE == CRYPT_DES_MODE_MCRYPT ) { - $td = mcrypt_module_open(MCRYPT_DES, $this->mcrypt[0], $this->mode, $this->mcrypt[1]); - mcrypt_generic_init($td, $this->keys, $this->encryptIV); - - $ciphertext = mcrypt_generic($td, $plaintext); - - mcrypt_generic_deinit($td); - mcrypt_module_close($td); - - if ($this->continuousBuffer) { - $this->encryptIV = substr($ciphertext, -8); - } - - return $ciphertext; - } - - if (!is_array($this->keys)) { - $this->keys = $this->_prepareKey("\0\0\0\0\0\0\0\0"); - } - - $ciphertext = ''; - switch ($this->mode) { - case CRYPT_DES_MODE_ECB: - for ($i = 0; $i < strlen($plaintext); $i+=8) { - $ciphertext.= $this->_processBlock(substr($plaintext, $i, 8), CRYPT_DES_ENCRYPT); - } - break; - case CRYPT_DES_MODE_CBC: - $xor = $this->encryptIV; - for ($i = 0; $i < strlen($plaintext); $i+=8) { - $block = substr($plaintext, $i, 8); - $block = $this->_processBlock($block ^ $xor, CRYPT_DES_ENCRYPT); - $xor = $block; - $ciphertext.= $block; - } - if ($this->continuousBuffer) { - $this->encryptIV = $xor; - } - } - - return $ciphertext; - } - - /** - * Decrypts a message. - * - * If strlen($ciphertext) is not a multiple of 8, null bytes will be added to the end of the string until it is. - * - * @see Crypt_DES::encrypt() - * @access public - * @param String $ciphertext - */ - function decrypt($ciphertext) - { - // we pad with chr(0) since that's what mcrypt_generic does. to quote from http://php.net/function.mcrypt-generic : - // "The data is padded with "\0" to make sure the length of the data is n * blocksize." - $ciphertext = str_pad($ciphertext, (strlen($ciphertext) + 7) & 0xFFFFFFF8, chr(0)); - - if ( CRYPT_DES_MODE == CRYPT_DES_MODE_MCRYPT ) { - $td = mcrypt_module_open(MCRYPT_DES, $this->mcrypt[0], $this->mode, $this->mcrypt[1]); - mcrypt_generic_init($td, $this->keys, $this->decryptIV); - - $plaintext = mdecrypt_generic($td, $ciphertext); - - mcrypt_generic_deinit($td); - mcrypt_module_close($td); - - if ($this->continuousBuffer) { - $this->decryptIV = substr($ciphertext, -8); - } - - return $this->_unpad($plaintext); - } - - if (!is_array($this->keys)) { - $this->keys = $this->_prepareKey("\0\0\0\0\0\0\0\0"); - } - - $plaintext = ''; - switch ($this->mode) { - case CRYPT_DES_MODE_ECB: - for ($i = 0; $i < strlen($ciphertext); $i+=8) { - $plaintext.= $this->_processBlock(substr($ciphertext, $i, 8), CRYPT_DES_DECRYPT); - } - break; - case CRYPT_DES_MODE_CBC: - $xor = $this->decryptIV; - for ($i = 0; $i < strlen($ciphertext); $i+=8) { - $block = substr($ciphertext, $i, 8); - $plaintext.= $this->_processBlock($block, CRYPT_DES_DECRYPT) ^ $xor; - $xor = $block; - } - if ($this->continuousBuffer) { - $this->decryptIV = $xor; - } - } - - return $this->_unpad($plaintext); - } - - /** - * Treat consecutive "packets" as if they are a continuous buffer. - * - * Say you have a 16-byte plaintext $plaintext. Using the default behavior, the two following code snippets - * will yield different outputs: - * - * - * echo $des->encrypt(substr($plaintext, 0, 8)); - * echo $des->encrypt(substr($plaintext, 8, 8)); - * - * - * echo $des->encrypt($plaintext); - * - * - * The solution is to enable the continuous buffer. Although this will resolve the above discrepancy, it creates - * another, as demonstrated with the following: - * - * - * $des->encrypt(substr($plaintext, 0, 8)); - * echo $des->decrypt($des->encrypt(substr($plaintext, 8, 8))); - * - * - * echo $des->decrypt($des->encrypt(substr($plaintext, 8, 8))); - * - * - * With the continuous buffer disabled, these would yield the same output. With it enabled, they yield different - * outputs. The reason is due to the fact that the initialization vector's change after every encryption / - * decryption round when the continuous buffer is enabled. When it's disabled, they remain constant. - * - * Put another way, when the continuous buffer is enabled, the state of the Crypt_DES() object changes after each - * encryption / decryption round, whereas otherwise, it'd remain constant. For this reason, it's recommended that - * continuous buffers not be used. They do offer better security and are, in fact, sometimes required (SSH uses them), - * however, they are also less intuitive and more likely to cause you problems. - * - * @see Crypt_DES::disableContinuousBuffer() - * @access public - */ - function enableContinuousBuffer() - { - $this->continuousBuffer = true; - } - - /** - * Treat consecutive packets as if they are a discontinuous buffer. - * - * The default behavior. - * - * @see Crypt_DES::enableContinuousBuffer() - * @access public - */ - function disableContinuousBuffer() - { - $this->continuousBuffer = false; - $this->encryptIV = $this->iv; - $this->decryptIV = $this->iv; - } - - /** - * Pad "packets". - * - * DES works by encrypting eight bytes at a time. If you ever need to encrypt or decrypt something that's not - * a multiple of eight, it becomes necessary to pad the input so that it's length is a multiple of eight. - * - * Padding is enabled by default. Sometimes, however, it is undesirable to pad strings. Such is the case in SSH1, - * where "packets" are padded with random bytes before being encrypted. Unpad these packets and you risk stripping - * away characters that shouldn't be stripped away. (SSH knows how many bytes are added because the length is - * transmitted separately) - * - * @see Crypt_DES::disablePadding() - * @access public - */ - function enablePadding() - { - $this->padding = true; - } - - /** - * Do not pad packets. - * - * @see Crypt_DES::enablePadding() - * @access public - */ - function disablePadding() - { - $this->padding = false; - } - - /** - * Pads a string - * - * Pads a string using the RSA PKCS padding standards so that its length is a multiple of the blocksize (8). - * 8 - (strlen($text) & 7) bytes are added, each of which is equal to chr(8 - (strlen($text) & 7) - * - * If padding is disabled and $text is not a multiple of the blocksize, the string will be padded regardless - * and padding will, hence forth, be enabled. - * - * @see Crypt_DES::_unpad() - * @access private - */ - function _pad($text) - { - $length = strlen($text); - - if (!$this->padding) { - if (($length & 7) == 0) { - return $text; - } else { - user_error("The plaintext's length ($length) is not a multiple of the block size (8)", E_USER_NOTICE); - $this->padding = true; - } - } - - $pad = 8 - ($length & 7); - return str_pad($text, $length + $pad, chr($pad)); - } - - /** - * Unpads a string - * - * If padding is enabled and the reported padding length is invalid, padding will be, hence forth, disabled. - * - * @see Crypt_DES::_pad() - * @access private - */ - function _unpad($text) - { - if (!$this->padding) { - return $text; - } - - $length = ord($text[strlen($text) - 1]); - - if (!$length || $length > 8) { - user_error("The number of bytes reported as being padded ($length) is invalid (block size = 8)", E_USER_NOTICE); - $this->padding = false; - return $text; - } - - return substr($text, 0, -$length); - } - - /** - * Encrypts or decrypts a 64-bit block - * - * $mode should be either CRYPT_DES_ENCRYPT or CRYPT_DES_DECRYPT. See - * {@link http://en.wikipedia.org/wiki/Image:Feistel.png Feistel.png} to get a general - * idea of what this function does. - * - * @access private - * @param String $block - * @param Integer $mode - * @return String - */ - function _processBlock($block, $mode) - { - // s-boxes. in the official DES docs, they're described as being matrices that - // one accesses by using the first and last bits to determine the row and the - // middle four bits to determine the column. in this implementation, they've - // been converted to vectors - static $sbox = array( - array( - 14, 0, 4, 15, 13, 7, 1, 4, 2, 14, 15, 2, 11, 13, 8, 1, - 3, 10 ,10, 6, 6, 12, 12, 11, 5, 9, 9, 5, 0, 3, 7, 8, - 4, 15, 1, 12, 14, 8, 8, 2, 13, 4, 6, 9, 2, 1, 11, 7, - 15, 5, 12, 11, 9, 3, 7, 14, 3, 10, 10, 0, 5, 6, 0, 13 - ), - array( - 15, 3, 1, 13, 8, 4, 14, 7, 6, 15, 11, 2, 3, 8, 4, 14, - 9, 12, 7, 0, 2, 1, 13, 10, 12, 6, 0, 9, 5, 11, 10, 5, - 0, 13, 14, 8, 7, 10, 11, 1, 10, 3, 4, 15, 13, 4, 1, 2, - 5, 11, 8, 6, 12, 7, 6, 12, 9, 0, 3, 5, 2, 14, 15, 9 - ), - array( - 10, 13, 0, 7, 9, 0, 14, 9, 6, 3, 3, 4, 15, 6, 5, 10, - 1, 2, 13, 8, 12, 5, 7, 14, 11, 12, 4, 11, 2, 15, 8, 1, - 13, 1, 6, 10, 4, 13, 9, 0, 8, 6, 15, 9, 3, 8, 0, 7, - 11, 4, 1, 15, 2, 14, 12, 3, 5, 11, 10, 5, 14, 2, 7, 12 - ), - array( - 7, 13, 13, 8, 14, 11, 3, 5, 0, 6, 6, 15, 9, 0, 10, 3, - 1, 4, 2, 7, 8, 2, 5, 12, 11, 1, 12, 10, 4, 14, 15, 9, - 10, 3, 6, 15, 9, 0, 0, 6, 12, 10, 11, 1, 7, 13, 13, 8, - 15, 9, 1, 4, 3, 5, 14, 11, 5, 12, 2, 7, 8, 2, 4, 14 - ), - array( - 2, 14, 12, 11, 4, 2, 1, 12, 7, 4, 10, 7, 11, 13, 6, 1, - 8, 5, 5, 0, 3, 15, 15, 10, 13, 3, 0, 9, 14, 8, 9, 6, - 4, 11, 2, 8, 1, 12, 11, 7, 10, 1, 13, 14, 7, 2, 8, 13, - 15, 6, 9, 15, 12, 0, 5, 9, 6, 10, 3, 4, 0, 5, 14, 3 - ), - array( - 12, 10, 1, 15, 10, 4, 15, 2, 9, 7, 2, 12, 6, 9, 8, 5, - 0, 6, 13, 1, 3, 13, 4, 14, 14, 0, 7, 11, 5, 3, 11, 8, - 9, 4, 14, 3, 15, 2, 5, 12, 2, 9, 8, 5, 12, 15, 3, 10, - 7, 11, 0, 14, 4, 1, 10, 7, 1, 6, 13, 0, 11, 8, 6, 13 - ), - array( - 4, 13, 11, 0, 2, 11, 14, 7, 15, 4, 0, 9, 8, 1, 13, 10, - 3, 14, 12, 3, 9, 5, 7, 12, 5, 2, 10, 15, 6, 8, 1, 6, - 1, 6, 4, 11, 11, 13, 13, 8, 12, 1, 3, 4, 7, 10, 14, 7, - 10, 9, 15, 5, 6, 0, 8, 15, 0, 14, 5, 2, 9, 3, 2, 12 - ), - array( - 13, 1, 2, 15, 8, 13, 4, 8, 6, 10, 15, 3, 11, 7, 1, 4, - 10, 12, 9, 5, 3, 6, 14, 11, 5, 0, 0, 14, 12, 9, 7, 2, - 7, 2, 11, 1, 4, 14, 1, 7, 9, 4, 12, 10, 14, 8, 2, 13, - 0, 15, 6, 12, 10, 9, 13, 0, 15, 3, 3, 5, 5, 6, 8, 11 - ) - ); - - $temp = unpack('Na/Nb', $block); - $block = array($temp['a'], $temp['b']); - - // because php does arithmetic right shifts, if the most significant bits are set, right - // shifting those into the correct position will add 1's - not 0's. this will intefere - // with the | operation unless a second & is done. so we isolate these bits and left shift - // them into place. we then & each block with 0x7FFFFFFF to prevennt 1's from being added - // for any other shifts. - $msb = array( - ($block[0] >> 31) & 1, - ($block[1] >> 31) & 1 - ); - $block[0] &= 0x7FFFFFFF; - $block[1] &= 0x7FFFFFFF; - - // we isolate the appropriate bit in the appropriate integer and shift as appropriate. in - // some cases, there are going to be multiple bits in the same integer that need to be shifted - // in the same way. we combine those into one shift operation. - $block = array( - (($block[1] & 0x00000040) << 25) | (($block[1] & 0x00004000) << 16) | - (($block[1] & 0x00400001) << 7) | (($block[1] & 0x40000100) >> 2) | - (($block[0] & 0x00000040) << 21) | (($block[0] & 0x00004000) << 12) | - (($block[0] & 0x00400001) << 3) | (($block[0] & 0x40000100) >> 6) | - (($block[1] & 0x00000010) << 19) | (($block[1] & 0x00001000) << 10) | - (($block[1] & 0x00100000) << 1) | (($block[1] & 0x10000000) >> 8) | - (($block[0] & 0x00000010) << 15) | (($block[0] & 0x00001000) << 6) | - (($block[0] & 0x00100000) >> 3) | (($block[0] & 0x10000000) >> 12) | - (($block[1] & 0x00000004) << 13) | (($block[1] & 0x00000400) << 4) | - (($block[1] & 0x00040000) >> 5) | (($block[1] & 0x04000000) >> 14) | - (($block[0] & 0x00000004) << 9) | ( $block[0] & 0x00000400 ) | - (($block[0] & 0x00040000) >> 9) | (($block[0] & 0x04000000) >> 18) | - (($block[1] & 0x00010000) >> 11) | (($block[1] & 0x01000000) >> 20) | - (($block[0] & 0x00010000) >> 15) | (($block[0] & 0x01000000) >> 24) - , - (($block[1] & 0x00000080) << 24) | (($block[1] & 0x00008000) << 15) | - (($block[1] & 0x00800002) << 6) | (($block[0] & 0x00000080) << 20) | - (($block[0] & 0x00008000) << 11) | (($block[0] & 0x00800002) << 2) | - (($block[1] & 0x00000020) << 18) | (($block[1] & 0x00002000) << 9) | - ( $block[1] & 0x00200000 ) | (($block[1] & 0x20000000) >> 9) | - (($block[0] & 0x00000020) << 14) | (($block[0] & 0x00002000) << 5) | - (($block[0] & 0x00200000) >> 4) | (($block[0] & 0x20000000) >> 13) | - (($block[1] & 0x00000008) << 12) | (($block[1] & 0x00000800) << 3) | - (($block[1] & 0x00080000) >> 6) | (($block[1] & 0x08000000) >> 15) | - (($block[0] & 0x00000008) << 8) | (($block[0] & 0x00000800) >> 1) | - (($block[0] & 0x00080000) >> 10) | (($block[0] & 0x08000000) >> 19) | - (($block[1] & 0x00000200) >> 3) | (($block[0] & 0x00000200) >> 7) | - (($block[1] & 0x00020000) >> 12) | (($block[1] & 0x02000000) >> 21) | - (($block[0] & 0x00020000) >> 16) | (($block[0] & 0x02000000) >> 25) | - ($msb[1] << 28) | ($msb[0] << 24) - ); - - for ($i = 0; $i < 16; $i++) { - // start of "the Feistel (F) function" - see the following URL: - // http://en.wikipedia.org/wiki/Image:Data_Encryption_Standard_InfoBox_Diagram.png - $temp = (($sbox[0][((($block[1] >> 27) & 0x1F) | (($block[1] & 1) << 5)) ^ $this->keys[$mode][$i][0]]) << 28) - | (($sbox[1][(($block[1] & 0x1F800000) >> 23) ^ $this->keys[$mode][$i][1]]) << 24) - | (($sbox[2][(($block[1] & 0x01F80000) >> 19) ^ $this->keys[$mode][$i][2]]) << 20) - | (($sbox[3][(($block[1] & 0x001F8000) >> 15) ^ $this->keys[$mode][$i][3]]) << 16) - | (($sbox[4][(($block[1] & 0x0001F800) >> 11) ^ $this->keys[$mode][$i][4]]) << 12) - | (($sbox[5][(($block[1] & 0x00001F80) >> 7) ^ $this->keys[$mode][$i][5]]) << 8) - | (($sbox[6][(($block[1] & 0x000001F8) >> 3) ^ $this->keys[$mode][$i][6]]) << 4) - | ( $sbox[7][((($block[1] & 0x1F) << 1) | (($block[1] >> 31) & 1)) ^ $this->keys[$mode][$i][7]]); - - $msb = ($temp >> 31) & 1; - $temp &= 0x7FFFFFFF; - $newBlock = (($temp & 0x00010000) << 15) | (($temp & 0x02020120) << 5) - | (($temp & 0x00001800) << 17) | (($temp & 0x01000000) >> 10) - | (($temp & 0x00000008) << 24) | (($temp & 0x00100000) << 6) - | (($temp & 0x00000010) << 21) | (($temp & 0x00008000) << 9) - | (($temp & 0x00000200) << 12) | (($temp & 0x10000000) >> 27) - | (($temp & 0x00000040) << 14) | (($temp & 0x08000000) >> 8) - | (($temp & 0x00004000) << 4) | (($temp & 0x00000002) << 16) - | (($temp & 0x00442000) >> 6) | (($temp & 0x40800000) >> 15) - | (($temp & 0x00000001) << 11) | (($temp & 0x20000000) >> 20) - | (($temp & 0x00080000) >> 13) | (($temp & 0x00000004) << 3) - | (($temp & 0x04000000) >> 22) | (($temp & 0x00000480) >> 7) - | (($temp & 0x00200000) >> 19) | ($msb << 23); - // end of "the Feistel (F) function" - $newBlock is F's output - - $temp = $block[1]; - $block[1] = $block[0] ^ $newBlock; - $block[0] = $temp; - } - - $msb = array( - ($block[0] >> 31) & 1, - ($block[1] >> 31) & 1 - ); - $block[0] &= 0x7FFFFFFF; - $block[1] &= 0x7FFFFFFF; - - $block = array( - (($block[0] & 0x01000004) << 7) | (($block[1] & 0x01000004) << 6) | - (($block[0] & 0x00010000) << 13) | (($block[1] & 0x00010000) << 12) | - (($block[0] & 0x00000100) << 19) | (($block[1] & 0x00000100) << 18) | - (($block[0] & 0x00000001) << 25) | (($block[1] & 0x00000001) << 24) | - (($block[0] & 0x02000008) >> 2) | (($block[1] & 0x02000008) >> 3) | - (($block[0] & 0x00020000) << 4) | (($block[1] & 0x00020000) << 3) | - (($block[0] & 0x00000200) << 10) | (($block[1] & 0x00000200) << 9) | - (($block[0] & 0x00000002) << 16) | (($block[1] & 0x00000002) << 15) | - (($block[0] & 0x04000000) >> 11) | (($block[1] & 0x04000000) >> 12) | - (($block[0] & 0x00040000) >> 5) | (($block[1] & 0x00040000) >> 6) | - (($block[0] & 0x00000400) << 1) | ( $block[1] & 0x00000400 ) | - (($block[0] & 0x08000000) >> 20) | (($block[1] & 0x08000000) >> 21) | - (($block[0] & 0x00080000) >> 14) | (($block[1] & 0x00080000) >> 15) | - (($block[0] & 0x00000800) >> 8) | (($block[1] & 0x00000800) >> 9) - , - (($block[0] & 0x10000040) << 3) | (($block[1] & 0x10000040) << 2) | - (($block[0] & 0x00100000) << 9) | (($block[1] & 0x00100000) << 8) | - (($block[0] & 0x00001000) << 15) | (($block[1] & 0x00001000) << 14) | - (($block[0] & 0x00000010) << 21) | (($block[1] & 0x00000010) << 20) | - (($block[0] & 0x20000080) >> 6) | (($block[1] & 0x20000080) >> 7) | - ( $block[0] & 0x00200000 ) | (($block[1] & 0x00200000) >> 1) | - (($block[0] & 0x00002000) << 6) | (($block[1] & 0x00002000) << 5) | - (($block[0] & 0x00000020) << 12) | (($block[1] & 0x00000020) << 11) | - (($block[0] & 0x40000000) >> 15) | (($block[1] & 0x40000000) >> 16) | - (($block[0] & 0x00400000) >> 9) | (($block[1] & 0x00400000) >> 10) | - (($block[0] & 0x00004000) >> 3) | (($block[1] & 0x00004000) >> 4) | - (($block[0] & 0x00800000) >> 18) | (($block[1] & 0x00800000) >> 19) | - (($block[0] & 0x00008000) >> 12) | (($block[1] & 0x00008000) >> 13) | - ($msb[0] << 7) | ($msb[1] << 6) - ); - - return pack('NN', $block[0], $block[1]); - } - - /** - * Creates the key schedule. - * - * @access private - * @param String $key - * @return Array - */ - function _prepareKey($key) - { - static $shifts = array( // number of key bits shifted per round - 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 - ); - - // pad the key and remove extra characters as appropriate. - $key = str_pad(substr($key, 0, 8), 8, chr(0)); - - $temp = unpack('Na/Nb', $key); - $key = array($temp['a'], $temp['b']); - $msb = array( - ($key[0] >> 31) & 1, - ($key[1] >> 31) & 1 - ); - $key[0] &= 0x7FFFFFFF; - $key[1] &= 0x7FFFFFFF; - - $key = array( - (($key[1] & 0x00000002) << 26) | (($key[1] & 0x00000204) << 17) | - (($key[1] & 0x00020408) << 8) | (($key[1] & 0x02040800) >> 1) | - (($key[0] & 0x00000002) << 22) | (($key[0] & 0x00000204) << 13) | - (($key[0] & 0x00020408) << 4) | (($key[0] & 0x02040800) >> 5) | - (($key[1] & 0x04080000) >> 10) | (($key[0] & 0x04080000) >> 14) | - (($key[1] & 0x08000000) >> 19) | (($key[0] & 0x08000000) >> 23) | - (($key[0] & 0x00000010) >> 1) | (($key[0] & 0x00001000) >> 10) | - (($key[0] & 0x00100000) >> 19) | (($key[0] & 0x10000000) >> 28) - , - (($key[1] & 0x00000080) << 20) | (($key[1] & 0x00008000) << 11) | - (($key[1] & 0x00800000) << 2) | (($key[0] & 0x00000080) << 16) | - (($key[0] & 0x00008000) << 7) | (($key[0] & 0x00800000) >> 2) | - (($key[1] & 0x00000040) << 13) | (($key[1] & 0x00004000) << 4) | - (($key[1] & 0x00400000) >> 5) | (($key[1] & 0x40000000) >> 14) | - (($key[0] & 0x00000040) << 9) | ( $key[0] & 0x00004000 ) | - (($key[0] & 0x00400000) >> 9) | (($key[0] & 0x40000000) >> 18) | - (($key[1] & 0x00000020) << 6) | (($key[1] & 0x00002000) >> 3) | - (($key[1] & 0x00200000) >> 12) | (($key[1] & 0x20000000) >> 21) | - (($key[0] & 0x00000020) << 2) | (($key[0] & 0x00002000) >> 7) | - (($key[0] & 0x00200000) >> 16) | (($key[0] & 0x20000000) >> 25) | - (($key[1] & 0x00000010) >> 1) | (($key[1] & 0x00001000) >> 10) | - (($key[1] & 0x00100000) >> 19) | (($key[1] & 0x10000000) >> 28) | - ($msb[1] << 24) | ($msb[0] << 20) - ); - - $keys = array(); - for ($i = 0; $i < 16; $i++) { - $key[0] <<= $shifts[$i]; - $temp = ($key[0] & 0xF0000000) >> 28; - $key[0] = ($key[0] | $temp) & 0x0FFFFFFF; - - $key[1] <<= $shifts[$i]; - $temp = ($key[1] & 0xF0000000) >> 28; - $key[1] = ($key[1] | $temp) & 0x0FFFFFFF; - - $temp = array( - (($key[1] & 0x00004000) >> 9) | (($key[1] & 0x00000800) >> 7) | - (($key[1] & 0x00020000) >> 14) | (($key[1] & 0x00000010) >> 2) | - (($key[1] & 0x08000000) >> 26) | (($key[1] & 0x00800000) >> 23) - , - (($key[1] & 0x02400000) >> 20) | (($key[1] & 0x00000001) << 4) | - (($key[1] & 0x00002000) >> 10) | (($key[1] & 0x00040000) >> 18) | - (($key[1] & 0x00000080) >> 6) - , - ( $key[1] & 0x00000020 ) | (($key[1] & 0x00000200) >> 5) | - (($key[1] & 0x00010000) >> 13) | (($key[1] & 0x01000000) >> 22) | - (($key[1] & 0x00000004) >> 1) | (($key[1] & 0x00100000) >> 20) - , - (($key[1] & 0x00001000) >> 7) | (($key[1] & 0x00200000) >> 17) | - (($key[1] & 0x00000002) << 2) | (($key[1] & 0x00000100) >> 6) | - (($key[1] & 0x00008000) >> 14) | (($key[1] & 0x04000000) >> 26) - , - (($key[0] & 0x00008000) >> 10) | ( $key[0] & 0x00000010 ) | - (($key[0] & 0x02000000) >> 22) | (($key[0] & 0x00080000) >> 17) | - (($key[0] & 0x00000200) >> 8) | (($key[0] & 0x00000002) >> 1) - , - (($key[0] & 0x04000000) >> 21) | (($key[0] & 0x00010000) >> 12) | - (($key[0] & 0x00000020) >> 2) | (($key[0] & 0x00000800) >> 9) | - (($key[0] & 0x00800000) >> 22) | (($key[0] & 0x00000100) >> 8) - , - (($key[0] & 0x00001000) >> 7) | (($key[0] & 0x00000088) >> 3) | - (($key[0] & 0x00020000) >> 14) | (($key[0] & 0x00000001) << 2) | - (($key[0] & 0x00400000) >> 21) - , - (($key[0] & 0x00000400) >> 5) | (($key[0] & 0x00004000) >> 10) | - (($key[0] & 0x00000040) >> 3) | (($key[0] & 0x00100000) >> 18) | - (($key[0] & 0x08000000) >> 26) | (($key[0] & 0x01000000) >> 24) - ); - - $keys[] = $temp; - } - - $temp = array( - CRYPT_DES_ENCRYPT => $keys, - CRYPT_DES_DECRYPT => array_reverse($keys) - ); - - return $temp; - } -} - -// vim: ts=4:sw=4:et: + + * setKey('abcdefgh'); + * + * $size = 10 * 1024; + * $plaintext = ''; + * for ($i = 0; $i < $size; $i++) { + * $plaintext.= 'a'; + * } + * + * echo $des->decrypt($des->encrypt($plaintext)); + * ?> + * + * + * LICENSE: This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * @category Crypt + * @package Crypt_DES + * @author Jim Wigginton + * @copyright MMVII Jim Wigginton + * @license http://www.gnu.org/licenses/lgpl.txt + * @version $Id: DES.php,v 1.12 2010/02/09 06:10:26 terrafrost Exp $ + * @link http://phpseclib.sourceforge.net + */ + +/**#@+ + * @access private + * @see Crypt_DES::_prepareKey() + * @see Crypt_DES::_processBlock() + */ +/** + * Contains array_reverse($keys[CRYPT_DES_DECRYPT]) + */ +define('CRYPT_DES_ENCRYPT', 0); +/** + * Contains array_reverse($keys[CRYPT_DES_ENCRYPT]) + */ +define('CRYPT_DES_DECRYPT', 1); +/**#@-*/ + +/**#@+ + * @access public + * @see Crypt_DES::encrypt() + * @see Crypt_DES::decrypt() + */ +/** + * Encrypt / decrypt using the Counter mode. + * + * Set to -1 since that's what Crypt/Random.php uses to index the CTR mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Counter_.28CTR.29 + */ +define('CRYPT_DES_MODE_CTR', -1); +/** + * Encrypt / decrypt using the Electronic Code Book mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29 + */ +define('CRYPT_DES_MODE_ECB', 1); +/** + * Encrypt / decrypt using the Code Book Chaining mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29 + */ +define('CRYPT_DES_MODE_CBC', 2); +/**#@-*/ + +/**#@+ + * @access private + * @see Crypt_DES::Crypt_DES() + */ +/** + * Toggles the internal implementation + */ +define('CRYPT_DES_MODE_INTERNAL', 1); +/** + * Toggles the mcrypt implementation + */ +define('CRYPT_DES_MODE_MCRYPT', 2); +/**#@-*/ + +/** + * Pure-PHP implementation of DES. + * + * @author Jim Wigginton + * @version 0.1.0 + * @access public + * @package Crypt_DES + */ +class Crypt_DES { + /** + * The Key Schedule + * + * @see Crypt_DES::setKey() + * @var Array + * @access private + */ + var $keys = "\0\0\0\0\0\0\0\0"; + + /** + * The Encryption Mode + * + * @see Crypt_DES::Crypt_DES() + * @var Integer + * @access private + */ + var $mode; + + /** + * Continuous Buffer status + * + * @see Crypt_DES::enableContinuousBuffer() + * @var Boolean + * @access private + */ + var $continuousBuffer = false; + + /** + * Padding status + * + * @see Crypt_DES::enablePadding() + * @var Boolean + * @access private + */ + var $padding = true; + + /** + * The Initialization Vector + * + * @see Crypt_DES::setIV() + * @var String + * @access private + */ + var $iv = "\0\0\0\0\0\0\0\0"; + + /** + * A "sliding" Initialization Vector + * + * @see Crypt_DES::enableContinuousBuffer() + * @var String + * @access private + */ + var $encryptIV = "\0\0\0\0\0\0\0\0"; + + /** + * A "sliding" Initialization Vector + * + * @see Crypt_DES::enableContinuousBuffer() + * @var String + * @access private + */ + var $decryptIV = "\0\0\0\0\0\0\0\0"; + + /** + * mcrypt resource for encryption + * + * The mcrypt resource can be recreated every time something needs to be created or it can be created just once. + * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode. + * + * @see Crypt_AES::encrypt() + * @var String + * @access private + */ + var $enmcrypt; + + /** + * mcrypt resource for decryption + * + * The mcrypt resource can be recreated every time something needs to be created or it can be created just once. + * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode. + * + * @see Crypt_AES::decrypt() + * @var String + * @access private + */ + var $demcrypt; + + /** + * Does the (en|de)mcrypt resource need to be (re)initialized? + * + * @see setKey() + * @see setIV() + * @var Boolean + * @access private + */ + var $changed = true; + + /** + * Default Constructor. + * + * Determines whether or not the mcrypt extension should be used. $mode should only, at present, be + * CRYPT_DES_MODE_ECB or CRYPT_DES_MODE_CBC. If not explictly set, CRYPT_DES_MODE_CBC will be used. + * + * @param optional Integer $mode + * @return Crypt_DES + * @access public + */ + function Crypt_DES($mode = CRYPT_MODE_DES_CBC) + { + if ( !defined('CRYPT_DES_MODE') ) { + switch (true) { + case extension_loaded('mcrypt'): + // i'd check to see if des was supported, by doing in_array('des', mcrypt_list_algorithms('')), + // but since that can be changed after the object has been created, there doesn't seem to be + // a lot of point... + define('CRYPT_DES_MODE', CRYPT_DES_MODE_MCRYPT); + break; + default: + define('CRYPT_DES_MODE', CRYPT_DES_MODE_INTERNAL); + } + } + + switch ( CRYPT_DES_MODE ) { + case CRYPT_DES_MODE_MCRYPT: + switch ($mode) { + case CRYPT_DES_MODE_ECB: + $this->mode = MCRYPT_MODE_ECB; + break; + case CRYPT_DES_MODE_CTR: + $this->mode = 'ctr'; + //$this->mode = in_array('ctr', mcrypt_list_modes()) ? 'ctr' : CRYPT_DES_MODE_CTR; + break; + case CRYPT_DES_MODE_CBC: + default: + $this->mode = MCRYPT_MODE_CBC; + } + + break; + default: + switch ($mode) { + case CRYPT_DES_MODE_ECB: + case CRYPT_DES_MODE_CTR: + case CRYPT_DES_MODE_CBC: + $this->mode = $mode; + break; + default: + $this->mode = CRYPT_DES_MODE_CBC; + } + } + } + + /** + * Sets the key. + * + * Keys can be of any length. DES, itself, uses 64-bit keys (eg. strlen($key) == 8), however, we + * only use the first eight, if $key has more then eight characters in it, and pad $key with the + * null byte if it is less then eight characters long. + * + * DES also requires that every eighth bit be a parity bit, however, we'll ignore that. + * + * If the key is not explicitly set, it'll be assumed to be all zero's. + * + * @access public + * @param String $key + */ + function setKey($key) + { + $this->keys = ( CRYPT_DES_MODE == CRYPT_DES_MODE_MCRYPT ) ? substr($key, 0, 8) : $this->_prepareKey($key); + $this->changed = true; + } + + /** + * Sets the initialization vector. (optional) + * + * SetIV is not required when CRYPT_DES_MODE_ECB is being used. If not explictly set, it'll be assumed + * to be all zero's. + * + * @access public + * @param String $iv + */ + function setIV($iv) + { + $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($iv, 0, 8), 8, chr(0)); + $this->changed = true; + } + + /** + * Generate CTR XOR encryption key + * + * Encrypt the output of this and XOR it against the ciphertext / plaintext to get the + * plaintext / ciphertext in CTR mode. + * + * @see Crypt_DES::decrypt() + * @see Crypt_DES::encrypt() + * @access public + * @param Integer $length + * @param String $iv + */ + function _generate_xor($length, &$iv) + { + $xor = ''; + $num_blocks = ($length + 7) >> 3; + for ($i = 0; $i < $num_blocks; $i++) { + $xor.= $iv; + for ($j = 4; $j <= 8; $j+=4) { + $temp = substr($iv, -$j, 4); + switch ($temp) { + case "\xFF\xFF\xFF\xFF": + $iv = substr_replace($iv, "\x00\x00\x00\x00", -$j, 4); + break; + case "\x7F\xFF\xFF\xFF": + $iv = substr_replace($iv, "\x80\x00\x00\x00", -$j, 4); + break 2; + default: + extract(unpack('Ncount', $temp)); + $iv = substr_replace($iv, pack('N', $count + 1), -$j, 4); + break 2; + } + } + } + + return $xor; + } + + /** + * Encrypts a message. + * + * $plaintext will be padded with up to 8 additional bytes. Other DES implementations may or may not pad in the + * same manner. Other common approaches to padding and the reasons why it's necessary are discussed in the following + * URL: + * + * {@link http://www.di-mgt.com.au/cryptopad.html http://www.di-mgt.com.au/cryptopad.html} + * + * An alternative to padding is to, separately, send the length of the file. This is what SSH, in fact, does. + * strlen($plaintext) will still need to be a multiple of 8, however, arbitrary values can be added to make it that + * length. + * + * @see Crypt_DES::decrypt() + * @access public + * @param String $plaintext + */ + function encrypt($plaintext) + { + if ($this->mode != CRYPT_DES_MODE_CTR && $this->mode != 'ctr') { + $plaintext = $this->_pad($plaintext); + } + + if ( CRYPT_DES_MODE == CRYPT_DES_MODE_MCRYPT ) { + if ($this->changed) { + if (!isset($this->enmcrypt)) { + $this->enmcrypt = mcrypt_module_open(MCRYPT_DES, '', $this->mode, ''); + } + mcrypt_generic_init($this->enmcrypt, $this->keys, $this->encryptIV); + $this->changed = false; + } + + $ciphertext = mcrypt_generic($this->enmcrypt, $plaintext); + + if (!$this->continuousBuffer) { + mcrypt_generic_init($this->enmcrypt, $this->keys, $this->encryptIV); + } + + return $ciphertext; + } + + if (!is_array($this->keys)) { + $this->keys = $this->_prepareKey("\0\0\0\0\0\0\0\0"); + } + + $ciphertext = ''; + switch ($this->mode) { + case CRYPT_DES_MODE_ECB: + for ($i = 0; $i < strlen($plaintext); $i+=8) { + $ciphertext.= $this->_processBlock(substr($plaintext, $i, 8), CRYPT_DES_ENCRYPT); + } + break; + case CRYPT_DES_MODE_CBC: + $xor = $this->encryptIV; + for ($i = 0; $i < strlen($plaintext); $i+=8) { + $block = substr($plaintext, $i, 8); + $block = $this->_processBlock($block ^ $xor, CRYPT_DES_ENCRYPT); + $xor = $block; + $ciphertext.= $block; + } + if ($this->continuousBuffer) { + $this->encryptIV = $xor; + } + break; + case CRYPT_DES_MODE_CTR: + $xor = $this->encryptIV; + for ($i = 0; $i < strlen($plaintext); $i+=8) { + $block = substr($plaintext, $i, 8); + $key = $this->_processBlock($this->_generate_xor(8, $xor), CRYPT_DES_ENCRYPT); + $ciphertext.= $block ^ $key; + } + if ($this->continuousBuffer) { + $this->encryptIV = $xor; + } + } + + return $ciphertext; + } + + /** + * Decrypts a message. + * + * If strlen($ciphertext) is not a multiple of 8, null bytes will be added to the end of the string until it is. + * + * @see Crypt_DES::encrypt() + * @access public + * @param String $ciphertext + */ + function decrypt($ciphertext) + { + if ($this->mode != CRYPT_DES_MODE_CTR && $this->mode != 'ctr') { + // we pad with chr(0) since that's what mcrypt_generic does. to quote from http://php.net/function.mcrypt-generic : + // "The data is padded with "\0" to make sure the length of the data is n * blocksize." + $ciphertext = str_pad($ciphertext, (strlen($ciphertext) + 7) & 0xFFFFFFF8, chr(0)); + } + + if ( CRYPT_DES_MODE == CRYPT_DES_MODE_MCRYPT ) { + if ($this->changed) { + if (!isset($this->demcrypt)) { + $this->demcrypt = mcrypt_module_open(MCRYPT_DES, '', $this->mode, ''); + } + mcrypt_generic_init($this->demcrypt, $this->keys, $this->decryptIV); + $this->changed = false; + } + + $plaintext = mdecrypt_generic($this->demcrypt, $ciphertext); + + if (!$this->continuousBuffer) { + mcrypt_generic_init($this->demcrypt, $this->keys, $this->decryptIV); + } + + return $this->mode != 'ctr' ? $this->_unpad($plaintext) : $plaintext; + } + + if (!is_array($this->keys)) { + $this->keys = $this->_prepareKey("\0\0\0\0\0\0\0\0"); + } + + $plaintext = ''; + switch ($this->mode) { + case CRYPT_DES_MODE_ECB: + for ($i = 0; $i < strlen($ciphertext); $i+=8) { + $plaintext.= $this->_processBlock(substr($ciphertext, $i, 8), CRYPT_DES_DECRYPT); + } + break; + case CRYPT_DES_MODE_CBC: + $xor = $this->decryptIV; + for ($i = 0; $i < strlen($ciphertext); $i+=8) { + $block = substr($ciphertext, $i, 8); + $plaintext.= $this->_processBlock($block, CRYPT_DES_DECRYPT) ^ $xor; + $xor = $block; + } + if ($this->continuousBuffer) { + $this->decryptIV = $xor; + } + break; + case CRYPT_DES_MODE_CTR: + $xor = $this->decryptIV; + for ($i = 0; $i < strlen($ciphertext); $i+=8) { + $block = substr($ciphertext, $i, 8); + $key = $this->_processBlock($this->_generate_xor(8, $xor), CRYPT_DES_ENCRYPT); + $plaintext.= $block ^ $key; + } + if ($this->continuousBuffer) { + $this->decryptIV = $xor; + } + } + + return $this->mode != CRYPT_DES_MODE_CTR ? $this->_unpad($plaintext) : $plaintext; + } + + /** + * Treat consecutive "packets" as if they are a continuous buffer. + * + * Say you have a 16-byte plaintext $plaintext. Using the default behavior, the two following code snippets + * will yield different outputs: + * + * + * echo $des->encrypt(substr($plaintext, 0, 8)); + * echo $des->encrypt(substr($plaintext, 8, 8)); + * + * + * echo $des->encrypt($plaintext); + * + * + * The solution is to enable the continuous buffer. Although this will resolve the above discrepancy, it creates + * another, as demonstrated with the following: + * + * + * $des->encrypt(substr($plaintext, 0, 8)); + * echo $des->decrypt($des->encrypt(substr($plaintext, 8, 8))); + * + * + * echo $des->decrypt($des->encrypt(substr($plaintext, 8, 8))); + * + * + * With the continuous buffer disabled, these would yield the same output. With it enabled, they yield different + * outputs. The reason is due to the fact that the initialization vector's change after every encryption / + * decryption round when the continuous buffer is enabled. When it's disabled, they remain constant. + * + * Put another way, when the continuous buffer is enabled, the state of the Crypt_DES() object changes after each + * encryption / decryption round, whereas otherwise, it'd remain constant. For this reason, it's recommended that + * continuous buffers not be used. They do offer better security and are, in fact, sometimes required (SSH uses them), + * however, they are also less intuitive and more likely to cause you problems. + * + * @see Crypt_DES::disableContinuousBuffer() + * @access public + */ + function enableContinuousBuffer() + { + $this->continuousBuffer = true; + } + + /** + * Treat consecutive packets as if they are a discontinuous buffer. + * + * The default behavior. + * + * @see Crypt_DES::enableContinuousBuffer() + * @access public + */ + function disableContinuousBuffer() + { + $this->continuousBuffer = false; + $this->encryptIV = $this->iv; + $this->decryptIV = $this->iv; + } + + /** + * Pad "packets". + * + * DES works by encrypting eight bytes at a time. If you ever need to encrypt or decrypt something that's not + * a multiple of eight, it becomes necessary to pad the input so that it's length is a multiple of eight. + * + * Padding is enabled by default. Sometimes, however, it is undesirable to pad strings. Such is the case in SSH1, + * where "packets" are padded with random bytes before being encrypted. Unpad these packets and you risk stripping + * away characters that shouldn't be stripped away. (SSH knows how many bytes are added because the length is + * transmitted separately) + * + * @see Crypt_DES::disablePadding() + * @access public + */ + function enablePadding() + { + $this->padding = true; + } + + /** + * Do not pad packets. + * + * @see Crypt_DES::enablePadding() + * @access public + */ + function disablePadding() + { + $this->padding = false; + } + + /** + * Pads a string + * + * Pads a string using the RSA PKCS padding standards so that its length is a multiple of the blocksize (8). + * 8 - (strlen($text) & 7) bytes are added, each of which is equal to chr(8 - (strlen($text) & 7) + * + * If padding is disabled and $text is not a multiple of the blocksize, the string will be padded regardless + * and padding will, hence forth, be enabled. + * + * @see Crypt_DES::_unpad() + * @access private + */ + function _pad($text) + { + $length = strlen($text); + + if (!$this->padding) { + if (($length & 7) == 0) { + return $text; + } else { + user_error("The plaintext's length ($length) is not a multiple of the block size (8)", E_USER_NOTICE); + $this->padding = true; + } + } + + $pad = 8 - ($length & 7); + return str_pad($text, $length + $pad, chr($pad)); + } + + /** + * Unpads a string + * + * If padding is enabled and the reported padding length is invalid the encryption key will be assumed to be wrong + * and false will be returned. + * + * @see Crypt_DES::_pad() + * @access private + */ + function _unpad($text) + { + if (!$this->padding) { + return $text; + } + + $length = ord($text[strlen($text) - 1]); + + if (!$length || $length > 8) { + return false; + } + + return substr($text, 0, -$length); + } + + /** + * Encrypts or decrypts a 64-bit block + * + * $mode should be either CRYPT_DES_ENCRYPT or CRYPT_DES_DECRYPT. See + * {@link http://en.wikipedia.org/wiki/Image:Feistel.png Feistel.png} to get a general + * idea of what this function does. + * + * @access private + * @param String $block + * @param Integer $mode + * @return String + */ + function _processBlock($block, $mode) + { + // s-boxes. in the official DES docs, they're described as being matrices that + // one accesses by using the first and last bits to determine the row and the + // middle four bits to determine the column. in this implementation, they've + // been converted to vectors + static $sbox = array( + array( + 14, 0, 4, 15, 13, 7, 1, 4, 2, 14, 15, 2, 11, 13, 8, 1, + 3, 10 ,10, 6, 6, 12, 12, 11, 5, 9, 9, 5, 0, 3, 7, 8, + 4, 15, 1, 12, 14, 8, 8, 2, 13, 4, 6, 9, 2, 1, 11, 7, + 15, 5, 12, 11, 9, 3, 7, 14, 3, 10, 10, 0, 5, 6, 0, 13 + ), + array( + 15, 3, 1, 13, 8, 4, 14, 7, 6, 15, 11, 2, 3, 8, 4, 14, + 9, 12, 7, 0, 2, 1, 13, 10, 12, 6, 0, 9, 5, 11, 10, 5, + 0, 13, 14, 8, 7, 10, 11, 1, 10, 3, 4, 15, 13, 4, 1, 2, + 5, 11, 8, 6, 12, 7, 6, 12, 9, 0, 3, 5, 2, 14, 15, 9 + ), + array( + 10, 13, 0, 7, 9, 0, 14, 9, 6, 3, 3, 4, 15, 6, 5, 10, + 1, 2, 13, 8, 12, 5, 7, 14, 11, 12, 4, 11, 2, 15, 8, 1, + 13, 1, 6, 10, 4, 13, 9, 0, 8, 6, 15, 9, 3, 8, 0, 7, + 11, 4, 1, 15, 2, 14, 12, 3, 5, 11, 10, 5, 14, 2, 7, 12 + ), + array( + 7, 13, 13, 8, 14, 11, 3, 5, 0, 6, 6, 15, 9, 0, 10, 3, + 1, 4, 2, 7, 8, 2, 5, 12, 11, 1, 12, 10, 4, 14, 15, 9, + 10, 3, 6, 15, 9, 0, 0, 6, 12, 10, 11, 1, 7, 13, 13, 8, + 15, 9, 1, 4, 3, 5, 14, 11, 5, 12, 2, 7, 8, 2, 4, 14 + ), + array( + 2, 14, 12, 11, 4, 2, 1, 12, 7, 4, 10, 7, 11, 13, 6, 1, + 8, 5, 5, 0, 3, 15, 15, 10, 13, 3, 0, 9, 14, 8, 9, 6, + 4, 11, 2, 8, 1, 12, 11, 7, 10, 1, 13, 14, 7, 2, 8, 13, + 15, 6, 9, 15, 12, 0, 5, 9, 6, 10, 3, 4, 0, 5, 14, 3 + ), + array( + 12, 10, 1, 15, 10, 4, 15, 2, 9, 7, 2, 12, 6, 9, 8, 5, + 0, 6, 13, 1, 3, 13, 4, 14, 14, 0, 7, 11, 5, 3, 11, 8, + 9, 4, 14, 3, 15, 2, 5, 12, 2, 9, 8, 5, 12, 15, 3, 10, + 7, 11, 0, 14, 4, 1, 10, 7, 1, 6, 13, 0, 11, 8, 6, 13 + ), + array( + 4, 13, 11, 0, 2, 11, 14, 7, 15, 4, 0, 9, 8, 1, 13, 10, + 3, 14, 12, 3, 9, 5, 7, 12, 5, 2, 10, 15, 6, 8, 1, 6, + 1, 6, 4, 11, 11, 13, 13, 8, 12, 1, 3, 4, 7, 10, 14, 7, + 10, 9, 15, 5, 6, 0, 8, 15, 0, 14, 5, 2, 9, 3, 2, 12 + ), + array( + 13, 1, 2, 15, 8, 13, 4, 8, 6, 10, 15, 3, 11, 7, 1, 4, + 10, 12, 9, 5, 3, 6, 14, 11, 5, 0, 0, 14, 12, 9, 7, 2, + 7, 2, 11, 1, 4, 14, 1, 7, 9, 4, 12, 10, 14, 8, 2, 13, + 0, 15, 6, 12, 10, 9, 13, 0, 15, 3, 3, 5, 5, 6, 8, 11 + ) + ); + + $keys = $this->keys; + + $temp = unpack('Na/Nb', $block); + $block = array($temp['a'], $temp['b']); + + // because php does arithmetic right shifts, if the most significant bits are set, right + // shifting those into the correct position will add 1's - not 0's. this will intefere + // with the | operation unless a second & is done. so we isolate these bits and left shift + // them into place. we then & each block with 0x7FFFFFFF to prevennt 1's from being added + // for any other shifts. + $msb = array( + ($block[0] >> 31) & 1, + ($block[1] >> 31) & 1 + ); + $block[0] &= 0x7FFFFFFF; + $block[1] &= 0x7FFFFFFF; + + // we isolate the appropriate bit in the appropriate integer and shift as appropriate. in + // some cases, there are going to be multiple bits in the same integer that need to be shifted + // in the same way. we combine those into one shift operation. + $block = array( + (($block[1] & 0x00000040) << 25) | (($block[1] & 0x00004000) << 16) | + (($block[1] & 0x00400001) << 7) | (($block[1] & 0x40000100) >> 2) | + (($block[0] & 0x00000040) << 21) | (($block[0] & 0x00004000) << 12) | + (($block[0] & 0x00400001) << 3) | (($block[0] & 0x40000100) >> 6) | + (($block[1] & 0x00000010) << 19) | (($block[1] & 0x00001000) << 10) | + (($block[1] & 0x00100000) << 1) | (($block[1] & 0x10000000) >> 8) | + (($block[0] & 0x00000010) << 15) | (($block[0] & 0x00001000) << 6) | + (($block[0] & 0x00100000) >> 3) | (($block[0] & 0x10000000) >> 12) | + (($block[1] & 0x00000004) << 13) | (($block[1] & 0x00000400) << 4) | + (($block[1] & 0x00040000) >> 5) | (($block[1] & 0x04000000) >> 14) | + (($block[0] & 0x00000004) << 9) | ( $block[0] & 0x00000400 ) | + (($block[0] & 0x00040000) >> 9) | (($block[0] & 0x04000000) >> 18) | + (($block[1] & 0x00010000) >> 11) | (($block[1] & 0x01000000) >> 20) | + (($block[0] & 0x00010000) >> 15) | (($block[0] & 0x01000000) >> 24) + , + (($block[1] & 0x00000080) << 24) | (($block[1] & 0x00008000) << 15) | + (($block[1] & 0x00800002) << 6) | (($block[0] & 0x00000080) << 20) | + (($block[0] & 0x00008000) << 11) | (($block[0] & 0x00800002) << 2) | + (($block[1] & 0x00000020) << 18) | (($block[1] & 0x00002000) << 9) | + ( $block[1] & 0x00200000 ) | (($block[1] & 0x20000000) >> 9) | + (($block[0] & 0x00000020) << 14) | (($block[0] & 0x00002000) << 5) | + (($block[0] & 0x00200000) >> 4) | (($block[0] & 0x20000000) >> 13) | + (($block[1] & 0x00000008) << 12) | (($block[1] & 0x00000800) << 3) | + (($block[1] & 0x00080000) >> 6) | (($block[1] & 0x08000000) >> 15) | + (($block[0] & 0x00000008) << 8) | (($block[0] & 0x00000800) >> 1) | + (($block[0] & 0x00080000) >> 10) | (($block[0] & 0x08000000) >> 19) | + (($block[1] & 0x00000200) >> 3) | (($block[0] & 0x00000200) >> 7) | + (($block[1] & 0x00020000) >> 12) | (($block[1] & 0x02000000) >> 21) | + (($block[0] & 0x00020000) >> 16) | (($block[0] & 0x02000000) >> 25) | + ($msb[1] << 28) | ($msb[0] << 24) + ); + + for ($i = 0; $i < 16; $i++) { + // start of "the Feistel (F) function" - see the following URL: + // http://en.wikipedia.org/wiki/Image:Data_Encryption_Standard_InfoBox_Diagram.png + $temp = (($sbox[0][((($block[1] >> 27) & 0x1F) | (($block[1] & 1) << 5)) ^ $keys[$mode][$i][0]]) << 28) + | (($sbox[1][(($block[1] & 0x1F800000) >> 23) ^ $keys[$mode][$i][1]]) << 24) + | (($sbox[2][(($block[1] & 0x01F80000) >> 19) ^ $keys[$mode][$i][2]]) << 20) + | (($sbox[3][(($block[1] & 0x001F8000) >> 15) ^ $keys[$mode][$i][3]]) << 16) + | (($sbox[4][(($block[1] & 0x0001F800) >> 11) ^ $keys[$mode][$i][4]]) << 12) + | (($sbox[5][(($block[1] & 0x00001F80) >> 7) ^ $keys[$mode][$i][5]]) << 8) + | (($sbox[6][(($block[1] & 0x000001F8) >> 3) ^ $keys[$mode][$i][6]]) << 4) + | ( $sbox[7][((($block[1] & 0x1F) << 1) | (($block[1] >> 31) & 1)) ^ $keys[$mode][$i][7]]); + + $msb = ($temp >> 31) & 1; + $temp &= 0x7FFFFFFF; + $newBlock = (($temp & 0x00010000) << 15) | (($temp & 0x02020120) << 5) + | (($temp & 0x00001800) << 17) | (($temp & 0x01000000) >> 10) + | (($temp & 0x00000008) << 24) | (($temp & 0x00100000) << 6) + | (($temp & 0x00000010) << 21) | (($temp & 0x00008000) << 9) + | (($temp & 0x00000200) << 12) | (($temp & 0x10000000) >> 27) + | (($temp & 0x00000040) << 14) | (($temp & 0x08000000) >> 8) + | (($temp & 0x00004000) << 4) | (($temp & 0x00000002) << 16) + | (($temp & 0x00442000) >> 6) | (($temp & 0x40800000) >> 15) + | (($temp & 0x00000001) << 11) | (($temp & 0x20000000) >> 20) + | (($temp & 0x00080000) >> 13) | (($temp & 0x00000004) << 3) + | (($temp & 0x04000000) >> 22) | (($temp & 0x00000480) >> 7) + | (($temp & 0x00200000) >> 19) | ($msb << 23); + // end of "the Feistel (F) function" - $newBlock is F's output + + $temp = $block[1]; + $block[1] = $block[0] ^ $newBlock; + $block[0] = $temp; + } + + $msb = array( + ($block[0] >> 31) & 1, + ($block[1] >> 31) & 1 + ); + $block[0] &= 0x7FFFFFFF; + $block[1] &= 0x7FFFFFFF; + + $block = array( + (($block[0] & 0x01000004) << 7) | (($block[1] & 0x01000004) << 6) | + (($block[0] & 0x00010000) << 13) | (($block[1] & 0x00010000) << 12) | + (($block[0] & 0x00000100) << 19) | (($block[1] & 0x00000100) << 18) | + (($block[0] & 0x00000001) << 25) | (($block[1] & 0x00000001) << 24) | + (($block[0] & 0x02000008) >> 2) | (($block[1] & 0x02000008) >> 3) | + (($block[0] & 0x00020000) << 4) | (($block[1] & 0x00020000) << 3) | + (($block[0] & 0x00000200) << 10) | (($block[1] & 0x00000200) << 9) | + (($block[0] & 0x00000002) << 16) | (($block[1] & 0x00000002) << 15) | + (($block[0] & 0x04000000) >> 11) | (($block[1] & 0x04000000) >> 12) | + (($block[0] & 0x00040000) >> 5) | (($block[1] & 0x00040000) >> 6) | + (($block[0] & 0x00000400) << 1) | ( $block[1] & 0x00000400 ) | + (($block[0] & 0x08000000) >> 20) | (($block[1] & 0x08000000) >> 21) | + (($block[0] & 0x00080000) >> 14) | (($block[1] & 0x00080000) >> 15) | + (($block[0] & 0x00000800) >> 8) | (($block[1] & 0x00000800) >> 9) + , + (($block[0] & 0x10000040) << 3) | (($block[1] & 0x10000040) << 2) | + (($block[0] & 0x00100000) << 9) | (($block[1] & 0x00100000) << 8) | + (($block[0] & 0x00001000) << 15) | (($block[1] & 0x00001000) << 14) | + (($block[0] & 0x00000010) << 21) | (($block[1] & 0x00000010) << 20) | + (($block[0] & 0x20000080) >> 6) | (($block[1] & 0x20000080) >> 7) | + ( $block[0] & 0x00200000 ) | (($block[1] & 0x00200000) >> 1) | + (($block[0] & 0x00002000) << 6) | (($block[1] & 0x00002000) << 5) | + (($block[0] & 0x00000020) << 12) | (($block[1] & 0x00000020) << 11) | + (($block[0] & 0x40000000) >> 15) | (($block[1] & 0x40000000) >> 16) | + (($block[0] & 0x00400000) >> 9) | (($block[1] & 0x00400000) >> 10) | + (($block[0] & 0x00004000) >> 3) | (($block[1] & 0x00004000) >> 4) | + (($block[0] & 0x00800000) >> 18) | (($block[1] & 0x00800000) >> 19) | + (($block[0] & 0x00008000) >> 12) | (($block[1] & 0x00008000) >> 13) | + ($msb[0] << 7) | ($msb[1] << 6) + ); + + return pack('NN', $block[0], $block[1]); + } + + /** + * Creates the key schedule. + * + * @access private + * @param String $key + * @return Array + */ + function _prepareKey($key) + { + static $shifts = array( // number of key bits shifted per round + 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 + ); + + // pad the key and remove extra characters as appropriate. + $key = str_pad(substr($key, 0, 8), 8, chr(0)); + + $temp = unpack('Na/Nb', $key); + $key = array($temp['a'], $temp['b']); + $msb = array( + ($key[0] >> 31) & 1, + ($key[1] >> 31) & 1 + ); + $key[0] &= 0x7FFFFFFF; + $key[1] &= 0x7FFFFFFF; + + $key = array( + (($key[1] & 0x00000002) << 26) | (($key[1] & 0x00000204) << 17) | + (($key[1] & 0x00020408) << 8) | (($key[1] & 0x02040800) >> 1) | + (($key[0] & 0x00000002) << 22) | (($key[0] & 0x00000204) << 13) | + (($key[0] & 0x00020408) << 4) | (($key[0] & 0x02040800) >> 5) | + (($key[1] & 0x04080000) >> 10) | (($key[0] & 0x04080000) >> 14) | + (($key[1] & 0x08000000) >> 19) | (($key[0] & 0x08000000) >> 23) | + (($key[0] & 0x00000010) >> 1) | (($key[0] & 0x00001000) >> 10) | + (($key[0] & 0x00100000) >> 19) | (($key[0] & 0x10000000) >> 28) + , + (($key[1] & 0x00000080) << 20) | (($key[1] & 0x00008000) << 11) | + (($key[1] & 0x00800000) << 2) | (($key[0] & 0x00000080) << 16) | + (($key[0] & 0x00008000) << 7) | (($key[0] & 0x00800000) >> 2) | + (($key[1] & 0x00000040) << 13) | (($key[1] & 0x00004000) << 4) | + (($key[1] & 0x00400000) >> 5) | (($key[1] & 0x40000000) >> 14) | + (($key[0] & 0x00000040) << 9) | ( $key[0] & 0x00004000 ) | + (($key[0] & 0x00400000) >> 9) | (($key[0] & 0x40000000) >> 18) | + (($key[1] & 0x00000020) << 6) | (($key[1] & 0x00002000) >> 3) | + (($key[1] & 0x00200000) >> 12) | (($key[1] & 0x20000000) >> 21) | + (($key[0] & 0x00000020) << 2) | (($key[0] & 0x00002000) >> 7) | + (($key[0] & 0x00200000) >> 16) | (($key[0] & 0x20000000) >> 25) | + (($key[1] & 0x00000010) >> 1) | (($key[1] & 0x00001000) >> 10) | + (($key[1] & 0x00100000) >> 19) | (($key[1] & 0x10000000) >> 28) | + ($msb[1] << 24) | ($msb[0] << 20) + ); + + $keys = array(); + for ($i = 0; $i < 16; $i++) { + $key[0] <<= $shifts[$i]; + $temp = ($key[0] & 0xF0000000) >> 28; + $key[0] = ($key[0] | $temp) & 0x0FFFFFFF; + + $key[1] <<= $shifts[$i]; + $temp = ($key[1] & 0xF0000000) >> 28; + $key[1] = ($key[1] | $temp) & 0x0FFFFFFF; + + $temp = array( + (($key[1] & 0x00004000) >> 9) | (($key[1] & 0x00000800) >> 7) | + (($key[1] & 0x00020000) >> 14) | (($key[1] & 0x00000010) >> 2) | + (($key[1] & 0x08000000) >> 26) | (($key[1] & 0x00800000) >> 23) + , + (($key[1] & 0x02400000) >> 20) | (($key[1] & 0x00000001) << 4) | + (($key[1] & 0x00002000) >> 10) | (($key[1] & 0x00040000) >> 18) | + (($key[1] & 0x00000080) >> 6) + , + ( $key[1] & 0x00000020 ) | (($key[1] & 0x00000200) >> 5) | + (($key[1] & 0x00010000) >> 13) | (($key[1] & 0x01000000) >> 22) | + (($key[1] & 0x00000004) >> 1) | (($key[1] & 0x00100000) >> 20) + , + (($key[1] & 0x00001000) >> 7) | (($key[1] & 0x00200000) >> 17) | + (($key[1] & 0x00000002) << 2) | (($key[1] & 0x00000100) >> 6) | + (($key[1] & 0x00008000) >> 14) | (($key[1] & 0x04000000) >> 26) + , + (($key[0] & 0x00008000) >> 10) | ( $key[0] & 0x00000010 ) | + (($key[0] & 0x02000000) >> 22) | (($key[0] & 0x00080000) >> 17) | + (($key[0] & 0x00000200) >> 8) | (($key[0] & 0x00000002) >> 1) + , + (($key[0] & 0x04000000) >> 21) | (($key[0] & 0x00010000) >> 12) | + (($key[0] & 0x00000020) >> 2) | (($key[0] & 0x00000800) >> 9) | + (($key[0] & 0x00800000) >> 22) | (($key[0] & 0x00000100) >> 8) + , + (($key[0] & 0x00001000) >> 7) | (($key[0] & 0x00000088) >> 3) | + (($key[0] & 0x00020000) >> 14) | (($key[0] & 0x00000001) << 2) | + (($key[0] & 0x00400000) >> 21) + , + (($key[0] & 0x00000400) >> 5) | (($key[0] & 0x00004000) >> 10) | + (($key[0] & 0x00000040) >> 3) | (($key[0] & 0x00100000) >> 18) | + (($key[0] & 0x08000000) >> 26) | (($key[0] & 0x01000000) >> 24) + ); + + $keys[] = $temp; + } + + $temp = array( + CRYPT_DES_ENCRYPT => $keys, + CRYPT_DES_DECRYPT => array_reverse($keys) + ); + + return $temp; + } +} + +// vim: ts=4:sw=4:et: // vim6: fdl=1: \ No newline at end of file diff --git a/plugins/OStatus/extlib/Crypt/Hash.php b/plugins/OStatus/extlib/Crypt/Hash.php index ef3a85802..e4dfde331 100644 --- a/plugins/OStatus/extlib/Crypt/Hash.php +++ b/plugins/OStatus/extlib/Crypt/Hash.php @@ -1,816 +1,816 @@ - - * setKey('abcdefg'); - * - * echo base64_encode($hash->hash('abcdefg')); - * ?> - * - * - * LICENSE: This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, - * MA 02111-1307 USA - * - * @category Crypt - * @package Crypt_Hash - * @author Jim Wigginton - * @copyright MMVII Jim Wigginton - * @license http://www.gnu.org/licenses/lgpl.txt - * @version $Id: Hash.php,v 1.6 2009/11/23 23:37:07 terrafrost Exp $ - * @link http://phpseclib.sourceforge.net - */ - -/**#@+ - * @access private - * @see Crypt_Hash::Crypt_Hash() - */ -/** - * Toggles the internal implementation - */ -define('CRYPT_HASH_MODE_INTERNAL', 1); -/** - * Toggles the mhash() implementation, which has been deprecated on PHP 5.3.0+. - */ -define('CRYPT_HASH_MODE_MHASH', 2); -/** - * Toggles the hash() implementation, which works on PHP 5.1.2+. - */ -define('CRYPT_HASH_MODE_HASH', 3); -/**#@-*/ - -/** - * Pure-PHP implementations of keyed-hash message authentication codes (HMACs) and various cryptographic hashing functions. - * - * @author Jim Wigginton - * @version 0.1.0 - * @access public - * @package Crypt_Hash - */ -class Crypt_Hash { - /** - * Byte-length of compression blocks / key (Internal HMAC) - * - * @see Crypt_Hash::setAlgorithm() - * @var Integer - * @access private - */ - var $b; - - /** - * Byte-length of hash output (Internal HMAC) - * - * @see Crypt_Hash::setHash() - * @var Integer - * @access private - */ - var $l = false; - - /** - * Hash Algorithm - * - * @see Crypt_Hash::setHash() - * @var String - * @access private - */ - var $hash; - - /** - * Key - * - * @see Crypt_Hash::setKey() - * @var String - * @access private - */ - var $key = ''; - - /** - * Outer XOR (Internal HMAC) - * - * @see Crypt_Hash::setKey() - * @var String - * @access private - */ - var $opad; - - /** - * Inner XOR (Internal HMAC) - * - * @see Crypt_Hash::setKey() - * @var String - * @access private - */ - var $ipad; - - /** - * Default Constructor. - * - * @param optional String $hash - * @return Crypt_Hash - * @access public - */ - function Crypt_Hash($hash = 'sha1') - { - if ( !defined('CRYPT_HASH_MODE') ) { - switch (true) { - case extension_loaded('hash'): - define('CRYPT_HASH_MODE', CRYPT_HASH_MODE_HASH); - break; - case extension_loaded('mhash'): - define('CRYPT_HASH_MODE', CRYPT_HASH_MODE_MHASH); - break; - default: - define('CRYPT_HASH_MODE', CRYPT_HASH_MODE_INTERNAL); - } - } - - $this->setHash($hash); - } - - /** - * Sets the key for HMACs - * - * Keys can be of any length. - * - * @access public - * @param String $key - */ - function setKey($key) - { - $this->key = $key; - } - - /** - * Sets the hash function. - * - * @access public - * @param String $hash - */ - function setHash($hash) - { - switch ($hash) { - case 'md5-96': - case 'sha1-96': - $this->l = 12; // 96 / 8 = 12 - break; - case 'md2': - case 'md5': - $this->l = 16; - break; - case 'sha1': - $this->l = 20; - break; - case 'sha256': - $this->l = 32; - break; - case 'sha384': - $this->l = 48; - break; - case 'sha512': - $this->l = 64; - } - - switch ($hash) { - case 'md2': - $mode = CRYPT_HASH_MODE_INTERNAL; - break; - case 'sha384': - case 'sha512': - $mode = CRYPT_HASH_MODE == CRYPT_HASH_MODE_MHASH ? CRYPT_HASH_MODE_INTERNAL : CRYPT_HASH_MODE; - break; - default: - $mode = CRYPT_HASH_MODE; - } - - switch ( $mode ) { - case CRYPT_HASH_MODE_MHASH: - switch ($hash) { - case 'md5': - case 'md5-96': - $this->hash = MHASH_MD5; - break; - case 'sha256': - $this->hash = MHASH_SHA256; - break; - case 'sha1': - case 'sha1-96': - default: - $this->hash = MHASH_SHA1; - } - return; - case CRYPT_HASH_MODE_HASH: - switch ($hash) { - case 'md5': - case 'md5-96': - $this->hash = 'md5'; - return; - case 'sha256': - case 'sha384': - case 'sha512': - $this->hash = $hash; - return; - case 'sha1': - case 'sha1-96': - default: - $this->hash = 'sha1'; - } - return; - } - - switch ($hash) { - case 'md2': - $this->b = 16; - $this->hash = array($this, '_md2'); - break; - case 'md5': - case 'md5-96': - $this->b = 64; - $this->hash = array($this, '_md5'); - break; - case 'sha256': - $this->b = 64; - $this->hash = array($this, '_sha256'); - break; - case 'sha384': - case 'sha512': - $this->b = 128; - $this->hash = array($this, '_sha512'); - break; - case 'sha1': - case 'sha1-96': - default: - $this->b = 64; - $this->hash = array($this, '_sha1'); - } - - $this->ipad = str_repeat(chr(0x36), $this->b); - $this->opad = str_repeat(chr(0x5C), $this->b); - } - - /** - * Compute the HMAC. - * - * @access public - * @param String $text - * @return String - */ - function hash($text) - { - $mode = is_array($this->hash) ? CRYPT_HASH_MODE_INTERNAL : CRYPT_HASH_MODE; - - if (!empty($this->key)) { - switch ( $mode ) { - case CRYPT_HASH_MODE_MHASH: - $output = mhash($this->hash, $text, $this->key); - break; - case CRYPT_HASH_MODE_HASH: - $output = hash_hmac($this->hash, $text, $this->key, true); - break; - case CRYPT_HASH_MODE_INTERNAL: - /* "Applications that use keys longer than B bytes will first hash the key using H and then use the - resultant L byte string as the actual key to HMAC." - - -- http://tools.ietf.org/html/rfc2104#section-2 */ - $key = strlen($this->key) > $this->b ? call_user_func($this->$hash, $this->key) : $this->key; - - $key = str_pad($key, $this->b, chr(0)); // step 1 - $temp = $this->ipad ^ $key; // step 2 - $temp .= $text; // step 3 - $temp = call_user_func($this->hash, $temp); // step 4 - $output = $this->opad ^ $key; // step 5 - $output.= $temp; // step 6 - $output = call_user_func($this->hash, $output); // step 7 - } - } else { - switch ( $mode ) { - case CRYPT_HASH_MODE_MHASH: - $output = mhash($this->hash, $text); - break; - case CRYPT_HASH_MODE_HASH: - $output = hash($this->hash, $text, true); - break; - case CRYPT_HASH_MODE_INTERNAL: - $output = call_user_func($this->hash, $text); - } - } - - return substr($output, 0, $this->l); - } - - /** - * Returns the hash length (in bytes) - * - * @access private - * @return Integer - */ - function getLength() - { - return $this->l; - } - - /** - * Wrapper for MD5 - * - * @access private - * @param String $text - */ - function _md5($m) - { - return pack('H*', md5($m)); - } - - /** - * Wrapper for SHA1 - * - * @access private - * @param String $text - */ - function _sha1($m) - { - return pack('H*', sha1($m)); - } - - /** - * Pure-PHP implementation of MD2 - * - * See {@link http://tools.ietf.org/html/rfc1319 RFC1319}. - * - * @access private - * @param String $text - */ - function _md2($m) - { - static $s = array( - 41, 46, 67, 201, 162, 216, 124, 1, 61, 54, 84, 161, 236, 240, 6, - 19, 98, 167, 5, 243, 192, 199, 115, 140, 152, 147, 43, 217, 188, - 76, 130, 202, 30, 155, 87, 60, 253, 212, 224, 22, 103, 66, 111, 24, - 138, 23, 229, 18, 190, 78, 196, 214, 218, 158, 222, 73, 160, 251, - 245, 142, 187, 47, 238, 122, 169, 104, 121, 145, 21, 178, 7, 63, - 148, 194, 16, 137, 11, 34, 95, 33, 128, 127, 93, 154, 90, 144, 50, - 39, 53, 62, 204, 231, 191, 247, 151, 3, 255, 25, 48, 179, 72, 165, - 181, 209, 215, 94, 146, 42, 172, 86, 170, 198, 79, 184, 56, 210, - 150, 164, 125, 182, 118, 252, 107, 226, 156, 116, 4, 241, 69, 157, - 112, 89, 100, 113, 135, 32, 134, 91, 207, 101, 230, 45, 168, 2, 27, - 96, 37, 173, 174, 176, 185, 246, 28, 70, 97, 105, 52, 64, 126, 15, - 85, 71, 163, 35, 221, 81, 175, 58, 195, 92, 249, 206, 186, 197, - 234, 38, 44, 83, 13, 110, 133, 40, 132, 9, 211, 223, 205, 244, 65, - 129, 77, 82, 106, 220, 55, 200, 108, 193, 171, 250, 36, 225, 123, - 8, 12, 189, 177, 74, 120, 136, 149, 139, 227, 99, 232, 109, 233, - 203, 213, 254, 59, 0, 29, 57, 242, 239, 183, 14, 102, 88, 208, 228, - 166, 119, 114, 248, 235, 117, 75, 10, 49, 68, 80, 180, 143, 237, - 31, 26, 219, 153, 141, 51, 159, 17, 131, 20 - ); - - // Step 1. Append Padding Bytes - $pad = 16 - (strlen($m) & 0xF); - $m.= str_repeat(chr($pad), $pad); - - $length = strlen($m); - - // Step 2. Append Checksum - $c = str_repeat(chr(0), 16); - $l = chr(0); - for ($i = 0; $i < $length; $i+= 16) { - for ($j = 0; $j < 16; $j++) { - $c[$j] = chr($s[ord($m[$i + $j] ^ $l)]); - $l = $c[$j]; - } - } - $m.= $c; - - $length+= 16; - - // Step 3. Initialize MD Buffer - $x = str_repeat(chr(0), 48); - - // Step 4. Process Message in 16-Byte Blocks - for ($i = 0; $i < $length; $i+= 16) { - for ($j = 0; $j < 16; $j++) { - $x[$j + 16] = $m[$i + $j]; - $x[$j + 32] = $x[$j + 16] ^ $x[$j]; - } - $t = chr(0); - for ($j = 0; $j < 18; $j++) { - for ($k = 0; $k < 48; $k++) { - $x[$k] = $t = $x[$k] ^ chr($s[ord($t)]); - //$t = $x[$k] = $x[$k] ^ chr($s[ord($t)]); - } - $t = chr(ord($t) + $j); - } - } - - // Step 5. Output - return substr($x, 0, 16); - } - - /** - * Pure-PHP implementation of SHA256 - * - * See {@link http://en.wikipedia.org/wiki/SHA_hash_functions#SHA-256_.28a_SHA-2_variant.29_pseudocode SHA-256 (a SHA-2 variant) pseudocode - Wikipedia}. - * - * @access private - * @param String $text - */ - function _sha256($m) - { - if (extension_loaded('suhosin')) { - return pack('H*', sha256($m)); - } - - // Initialize variables - $hash = array( - 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 - ); - // Initialize table of round constants - // (first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311) - static $k = array( - 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, - 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, - 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, - 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, - 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, - 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, - 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, - 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 - ); - - // Pre-processing - $length = strlen($m); - // to round to nearest 56 mod 64, we'll add 64 - (length + (64 - 56)) % 64 - $m.= str_repeat(chr(0), 64 - (($length + 8) & 0x3F)); - $m[$length] = chr(0x80); - // we don't support hashing strings 512MB long - $m.= pack('N2', 0, $length << 3); - - // Process the message in successive 512-bit chunks - $chunks = str_split($m, 64); - foreach ($chunks as $chunk) { - $w = array(); - for ($i = 0; $i < 16; $i++) { - extract(unpack('Ntemp', $this->_string_shift($chunk, 4))); - $w[] = $temp; - } - - // Extend the sixteen 32-bit words into sixty-four 32-bit words - for ($i = 16; $i < 64; $i++) { - $s0 = $this->_rightRotate($w[$i - 15], 7) ^ - $this->_rightRotate($w[$i - 15], 18) ^ - $this->_rightShift( $w[$i - 15], 3); - $s1 = $this->_rightRotate($w[$i - 2], 17) ^ - $this->_rightRotate($w[$i - 2], 19) ^ - $this->_rightShift( $w[$i - 2], 10); - $w[$i] = $this->_add($w[$i - 16], $s0, $w[$i - 7], $s1); - - } - - // Initialize hash value for this chunk - list($a, $b, $c, $d, $e, $f, $g, $h) = $hash; - - // Main loop - for ($i = 0; $i < 64; $i++) { - $s0 = $this->_rightRotate($a, 2) ^ - $this->_rightRotate($a, 13) ^ - $this->_rightRotate($a, 22); - $maj = ($a & $b) ^ - ($a & $c) ^ - ($b & $c); - $t2 = $this->_add($s0, $maj); - - $s1 = $this->_rightRotate($e, 6) ^ - $this->_rightRotate($e, 11) ^ - $this->_rightRotate($e, 25); - $ch = ($e & $f) ^ - ($this->_not($e) & $g); - $t1 = $this->_add($h, $s1, $ch, $k[$i], $w[$i]); - - $h = $g; - $g = $f; - $f = $e; - $e = $this->_add($d, $t1); - $d = $c; - $c = $b; - $b = $a; - $a = $this->_add($t1, $t2); - } - - // Add this chunk's hash to result so far - $hash = array( - $this->_add($hash[0], $a), - $this->_add($hash[1], $b), - $this->_add($hash[2], $c), - $this->_add($hash[3], $d), - $this->_add($hash[4], $e), - $this->_add($hash[5], $f), - $this->_add($hash[6], $g), - $this->_add($hash[7], $h) - ); - } - - // Produce the final hash value (big-endian) - return pack('N8', $hash[0], $hash[1], $hash[2], $hash[3], $hash[4], $hash[5], $hash[6], $hash[7]); - } - - /** - * Pure-PHP implementation of SHA384 and SHA512 - * - * @access private - * @param String $text - */ - function _sha512($m) - { - if (!class_exists('Math_BigInteger')) { - require_once('Math/BigInteger.php'); - } - - static $init384, $init512, $k; - - if (!isset($k)) { - // Initialize variables - $init384 = array( // initial values for SHA384 - 'cbbb9d5dc1059ed8', '629a292a367cd507', '9159015a3070dd17', '152fecd8f70e5939', - '67332667ffc00b31', '8eb44a8768581511', 'db0c2e0d64f98fa7', '47b5481dbefa4fa4' - ); - $init512 = array( // initial values for SHA512 - '6a09e667f3bcc908', 'bb67ae8584caa73b', '3c6ef372fe94f82b', 'a54ff53a5f1d36f1', - '510e527fade682d1', '9b05688c2b3e6c1f', '1f83d9abfb41bd6b', '5be0cd19137e2179' - ); - - for ($i = 0; $i < 8; $i++) { - $init384[$i] = new Math_BigInteger($init384[$i], 16); - $init384[$i]->setPrecision(64); - $init512[$i] = new Math_BigInteger($init512[$i], 16); - $init512[$i]->setPrecision(64); - } - - // Initialize table of round constants - // (first 64 bits of the fractional parts of the cube roots of the first 80 primes 2..409) - $k = array( - '428a2f98d728ae22', '7137449123ef65cd', 'b5c0fbcfec4d3b2f', 'e9b5dba58189dbbc', - '3956c25bf348b538', '59f111f1b605d019', '923f82a4af194f9b', 'ab1c5ed5da6d8118', - 'd807aa98a3030242', '12835b0145706fbe', '243185be4ee4b28c', '550c7dc3d5ffb4e2', - '72be5d74f27b896f', '80deb1fe3b1696b1', '9bdc06a725c71235', 'c19bf174cf692694', - 'e49b69c19ef14ad2', 'efbe4786384f25e3', '0fc19dc68b8cd5b5', '240ca1cc77ac9c65', - '2de92c6f592b0275', '4a7484aa6ea6e483', '5cb0a9dcbd41fbd4', '76f988da831153b5', - '983e5152ee66dfab', 'a831c66d2db43210', 'b00327c898fb213f', 'bf597fc7beef0ee4', - 'c6e00bf33da88fc2', 'd5a79147930aa725', '06ca6351e003826f', '142929670a0e6e70', - '27b70a8546d22ffc', '2e1b21385c26c926', '4d2c6dfc5ac42aed', '53380d139d95b3df', - '650a73548baf63de', '766a0abb3c77b2a8', '81c2c92e47edaee6', '92722c851482353b', - 'a2bfe8a14cf10364', 'a81a664bbc423001', 'c24b8b70d0f89791', 'c76c51a30654be30', - 'd192e819d6ef5218', 'd69906245565a910', 'f40e35855771202a', '106aa07032bbd1b8', - '19a4c116b8d2d0c8', '1e376c085141ab53', '2748774cdf8eeb99', '34b0bcb5e19b48a8', - '391c0cb3c5c95a63', '4ed8aa4ae3418acb', '5b9cca4f7763e373', '682e6ff3d6b2b8a3', - '748f82ee5defb2fc', '78a5636f43172f60', '84c87814a1f0ab72', '8cc702081a6439ec', - '90befffa23631e28', 'a4506cebde82bde9', 'bef9a3f7b2c67915', 'c67178f2e372532b', - 'ca273eceea26619c', 'd186b8c721c0c207', 'eada7dd6cde0eb1e', 'f57d4f7fee6ed178', - '06f067aa72176fba', '0a637dc5a2c898a6', '113f9804bef90dae', '1b710b35131c471b', - '28db77f523047d84', '32caab7b40c72493', '3c9ebe0a15c9bebc', '431d67c49c100d4c', - '4cc5d4becb3e42b6', '597f299cfc657e2a', '5fcb6fab3ad6faec', '6c44198c4a475817' - ); - - for ($i = 0; $i < 80; $i++) { - $k[$i] = new Math_BigInteger($k[$i], 16); - } - } - - $hash = $this->l == 48 ? $init384 : $init512; - - // Pre-processing - $length = strlen($m); - // to round to nearest 112 mod 128, we'll add 128 - (length + (128 - 112)) % 128 - $m.= str_repeat(chr(0), 128 - (($length + 16) & 0x7F)); - $m[$length] = chr(0x80); - // we don't support hashing strings 512MB long - $m.= pack('N4', 0, 0, 0, $length << 3); - - // Process the message in successive 1024-bit chunks - $chunks = str_split($m, 128); - foreach ($chunks as $chunk) { - $w = array(); - for ($i = 0; $i < 16; $i++) { - $temp = new Math_BigInteger($this->_string_shift($chunk, 8), 256); - $temp->setPrecision(64); - $w[] = $temp; - } - - // Extend the sixteen 32-bit words into eighty 32-bit words - for ($i = 16; $i < 80; $i++) { - $temp = array( - $w[$i - 15]->bitwise_rightRotate(1), - $w[$i - 15]->bitwise_rightRotate(8), - $w[$i - 15]->bitwise_rightShift(7) - ); - $s0 = $temp[0]->bitwise_xor($temp[1]); - $s0 = $s0->bitwise_xor($temp[2]); - $temp = array( - $w[$i - 2]->bitwise_rightRotate(19), - $w[$i - 2]->bitwise_rightRotate(61), - $w[$i - 2]->bitwise_rightShift(6) - ); - $s1 = $temp[0]->bitwise_xor($temp[1]); - $s1 = $s1->bitwise_xor($temp[2]); - $w[$i] = $w[$i - 16]->copy(); - $w[$i] = $w[$i]->add($s0); - $w[$i] = $w[$i]->add($w[$i - 7]); - $w[$i] = $w[$i]->add($s1); - } - - // Initialize hash value for this chunk - $a = $hash[0]->copy(); - $b = $hash[1]->copy(); - $c = $hash[2]->copy(); - $d = $hash[3]->copy(); - $e = $hash[4]->copy(); - $f = $hash[5]->copy(); - $g = $hash[6]->copy(); - $h = $hash[7]->copy(); - - // Main loop - for ($i = 0; $i < 80; $i++) { - $temp = array( - $a->bitwise_rightRotate(28), - $a->bitwise_rightRotate(34), - $a->bitwise_rightRotate(39) - ); - $s0 = $temp[0]->bitwise_xor($temp[1]); - $s0 = $s0->bitwise_xor($temp[2]); - $temp = array( - $a->bitwise_and($b), - $a->bitwise_and($c), - $b->bitwise_and($c) - ); - $maj = $temp[0]->bitwise_xor($temp[1]); - $maj = $maj->bitwise_xor($temp[2]); - $t2 = $s0->add($maj); - - $temp = array( - $e->bitwise_rightRotate(14), - $e->bitwise_rightRotate(18), - $e->bitwise_rightRotate(41) - ); - $s1 = $temp[0]->bitwise_xor($temp[1]); - $s1 = $s1->bitwise_xor($temp[2]); - $temp = array( - $e->bitwise_and($f), - $g->bitwise_and($e->bitwise_not()) - ); - $ch = $temp[0]->bitwise_xor($temp[1]); - $t1 = $h->add($s1); - $t1 = $t1->add($ch); - $t1 = $t1->add($k[$i]); - $t1 = $t1->add($w[$i]); - - $h = $g->copy(); - $g = $f->copy(); - $f = $e->copy(); - $e = $d->add($t1); - $d = $c->copy(); - $c = $b->copy(); - $b = $a->copy(); - $a = $t1->add($t2); - } - - // Add this chunk's hash to result so far - $hash = array( - $hash[0]->add($a), - $hash[1]->add($b), - $hash[2]->add($c), - $hash[3]->add($d), - $hash[4]->add($e), - $hash[5]->add($f), - $hash[6]->add($g), - $hash[7]->add($h) - ); - } - - // Produce the final hash value (big-endian) - // (Crypt_Hash::hash() trims the output for hashes but not for HMACs. as such, we trim the output here) - $temp = $hash[0]->toBytes() . $hash[1]->toBytes() . $hash[2]->toBytes() . $hash[3]->toBytes() . - $hash[4]->toBytes() . $hash[5]->toBytes(); - if ($this->l != 48) { - $temp.= $hash[6]->toBytes() . $hash[7]->toBytes(); - } - - return $temp; - } - - /** - * Right Rotate - * - * @access private - * @param Integer $int - * @param Integer $amt - * @see _sha256() - * @return Integer - */ - function _rightRotate($int, $amt) - { - $invamt = 32 - $amt; - $mask = (1 << $invamt) - 1; - return (($int << $invamt) & 0xFFFFFFFF) | (($int >> $amt) & $mask); - } - - /** - * Right Shift - * - * @access private - * @param Integer $int - * @param Integer $amt - * @see _sha256() - * @return Integer - */ - function _rightShift($int, $amt) - { - $mask = (1 << (32 - $amt)) - 1; - return ($int >> $amt) & $mask; - } - - /** - * Not - * - * @access private - * @param Integer $int - * @see _sha256() - * @return Integer - */ - function _not($int) - { - return ~$int & 0xFFFFFFFF; - } - - /** - * Add - * - * _sha256() adds multiple unsigned 32-bit integers. Since PHP doesn't support unsigned integers and since the - * possibility of overflow exists, care has to be taken. Math_BigInteger() could be used but this should be faster. - * - * @param String $string - * @param optional Integer $index - * @return String - * @see _sha256() - * @access private - */ - function _add() - { - static $mod; - if (!isset($mod)) { - $mod = pow(2, 32); - } - - $result = 0; - $arguments = func_get_args(); - foreach ($arguments as $argument) { - $result+= $argument < 0 ? ($argument & 0x7FFFFFFF) + 0x80000000 : $argument; - } - - return fmod($result, $mod); - } - - /** - * String Shift - * - * Inspired by array_shift - * - * @param String $string - * @param optional Integer $index - * @return String - * @access private - */ - function _string_shift(&$string, $index = 1) - { - $substr = substr($string, 0, $index); - $string = substr($string, $index); - return $substr; - } + + * setKey('abcdefg'); + * + * echo base64_encode($hash->hash('abcdefg')); + * ?> + * + * + * LICENSE: This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * @category Crypt + * @package Crypt_Hash + * @author Jim Wigginton + * @copyright MMVII Jim Wigginton + * @license http://www.gnu.org/licenses/lgpl.txt + * @version $Id: Hash.php,v 1.6 2009/11/23 23:37:07 terrafrost Exp $ + * @link http://phpseclib.sourceforge.net + */ + +/**#@+ + * @access private + * @see Crypt_Hash::Crypt_Hash() + */ +/** + * Toggles the internal implementation + */ +define('CRYPT_HASH_MODE_INTERNAL', 1); +/** + * Toggles the mhash() implementation, which has been deprecated on PHP 5.3.0+. + */ +define('CRYPT_HASH_MODE_MHASH', 2); +/** + * Toggles the hash() implementation, which works on PHP 5.1.2+. + */ +define('CRYPT_HASH_MODE_HASH', 3); +/**#@-*/ + +/** + * Pure-PHP implementations of keyed-hash message authentication codes (HMACs) and various cryptographic hashing functions. + * + * @author Jim Wigginton + * @version 0.1.0 + * @access public + * @package Crypt_Hash + */ +class Crypt_Hash { + /** + * Byte-length of compression blocks / key (Internal HMAC) + * + * @see Crypt_Hash::setAlgorithm() + * @var Integer + * @access private + */ + var $b; + + /** + * Byte-length of hash output (Internal HMAC) + * + * @see Crypt_Hash::setHash() + * @var Integer + * @access private + */ + var $l = false; + + /** + * Hash Algorithm + * + * @see Crypt_Hash::setHash() + * @var String + * @access private + */ + var $hash; + + /** + * Key + * + * @see Crypt_Hash::setKey() + * @var String + * @access private + */ + var $key = ''; + + /** + * Outer XOR (Internal HMAC) + * + * @see Crypt_Hash::setKey() + * @var String + * @access private + */ + var $opad; + + /** + * Inner XOR (Internal HMAC) + * + * @see Crypt_Hash::setKey() + * @var String + * @access private + */ + var $ipad; + + /** + * Default Constructor. + * + * @param optional String $hash + * @return Crypt_Hash + * @access public + */ + function Crypt_Hash($hash = 'sha1') + { + if ( !defined('CRYPT_HASH_MODE') ) { + switch (true) { + case extension_loaded('hash'): + define('CRYPT_HASH_MODE', CRYPT_HASH_MODE_HASH); + break; + case extension_loaded('mhash'): + define('CRYPT_HASH_MODE', CRYPT_HASH_MODE_MHASH); + break; + default: + define('CRYPT_HASH_MODE', CRYPT_HASH_MODE_INTERNAL); + } + } + + $this->setHash($hash); + } + + /** + * Sets the key for HMACs + * + * Keys can be of any length. + * + * @access public + * @param String $key + */ + function setKey($key) + { + $this->key = $key; + } + + /** + * Sets the hash function. + * + * @access public + * @param String $hash + */ + function setHash($hash) + { + switch ($hash) { + case 'md5-96': + case 'sha1-96': + $this->l = 12; // 96 / 8 = 12 + break; + case 'md2': + case 'md5': + $this->l = 16; + break; + case 'sha1': + $this->l = 20; + break; + case 'sha256': + $this->l = 32; + break; + case 'sha384': + $this->l = 48; + break; + case 'sha512': + $this->l = 64; + } + + switch ($hash) { + case 'md2': + $mode = CRYPT_HASH_MODE_INTERNAL; + break; + case 'sha384': + case 'sha512': + $mode = CRYPT_HASH_MODE == CRYPT_HASH_MODE_MHASH ? CRYPT_HASH_MODE_INTERNAL : CRYPT_HASH_MODE; + break; + default: + $mode = CRYPT_HASH_MODE; + } + + switch ( $mode ) { + case CRYPT_HASH_MODE_MHASH: + switch ($hash) { + case 'md5': + case 'md5-96': + $this->hash = MHASH_MD5; + break; + case 'sha256': + $this->hash = MHASH_SHA256; + break; + case 'sha1': + case 'sha1-96': + default: + $this->hash = MHASH_SHA1; + } + return; + case CRYPT_HASH_MODE_HASH: + switch ($hash) { + case 'md5': + case 'md5-96': + $this->hash = 'md5'; + return; + case 'sha256': + case 'sha384': + case 'sha512': + $this->hash = $hash; + return; + case 'sha1': + case 'sha1-96': + default: + $this->hash = 'sha1'; + } + return; + } + + switch ($hash) { + case 'md2': + $this->b = 16; + $this->hash = array($this, '_md2'); + break; + case 'md5': + case 'md5-96': + $this->b = 64; + $this->hash = array($this, '_md5'); + break; + case 'sha256': + $this->b = 64; + $this->hash = array($this, '_sha256'); + break; + case 'sha384': + case 'sha512': + $this->b = 128; + $this->hash = array($this, '_sha512'); + break; + case 'sha1': + case 'sha1-96': + default: + $this->b = 64; + $this->hash = array($this, '_sha1'); + } + + $this->ipad = str_repeat(chr(0x36), $this->b); + $this->opad = str_repeat(chr(0x5C), $this->b); + } + + /** + * Compute the HMAC. + * + * @access public + * @param String $text + * @return String + */ + function hash($text) + { + $mode = is_array($this->hash) ? CRYPT_HASH_MODE_INTERNAL : CRYPT_HASH_MODE; + + if (!empty($this->key)) { + switch ( $mode ) { + case CRYPT_HASH_MODE_MHASH: + $output = mhash($this->hash, $text, $this->key); + break; + case CRYPT_HASH_MODE_HASH: + $output = hash_hmac($this->hash, $text, $this->key, true); + break; + case CRYPT_HASH_MODE_INTERNAL: + /* "Applications that use keys longer than B bytes will first hash the key using H and then use the + resultant L byte string as the actual key to HMAC." + + -- http://tools.ietf.org/html/rfc2104#section-2 */ + $key = strlen($this->key) > $this->b ? call_user_func($this->$hash, $this->key) : $this->key; + + $key = str_pad($key, $this->b, chr(0)); // step 1 + $temp = $this->ipad ^ $key; // step 2 + $temp .= $text; // step 3 + $temp = call_user_func($this->hash, $temp); // step 4 + $output = $this->opad ^ $key; // step 5 + $output.= $temp; // step 6 + $output = call_user_func($this->hash, $output); // step 7 + } + } else { + switch ( $mode ) { + case CRYPT_HASH_MODE_MHASH: + $output = mhash($this->hash, $text); + break; + case CRYPT_HASH_MODE_HASH: + $output = hash($this->hash, $text, true); + break; + case CRYPT_HASH_MODE_INTERNAL: + $output = call_user_func($this->hash, $text); + } + } + + return substr($output, 0, $this->l); + } + + /** + * Returns the hash length (in bytes) + * + * @access private + * @return Integer + */ + function getLength() + { + return $this->l; + } + + /** + * Wrapper for MD5 + * + * @access private + * @param String $text + */ + function _md5($m) + { + return pack('H*', md5($m)); + } + + /** + * Wrapper for SHA1 + * + * @access private + * @param String $text + */ + function _sha1($m) + { + return pack('H*', sha1($m)); + } + + /** + * Pure-PHP implementation of MD2 + * + * See {@link http://tools.ietf.org/html/rfc1319 RFC1319}. + * + * @access private + * @param String $text + */ + function _md2($m) + { + static $s = array( + 41, 46, 67, 201, 162, 216, 124, 1, 61, 54, 84, 161, 236, 240, 6, + 19, 98, 167, 5, 243, 192, 199, 115, 140, 152, 147, 43, 217, 188, + 76, 130, 202, 30, 155, 87, 60, 253, 212, 224, 22, 103, 66, 111, 24, + 138, 23, 229, 18, 190, 78, 196, 214, 218, 158, 222, 73, 160, 251, + 245, 142, 187, 47, 238, 122, 169, 104, 121, 145, 21, 178, 7, 63, + 148, 194, 16, 137, 11, 34, 95, 33, 128, 127, 93, 154, 90, 144, 50, + 39, 53, 62, 204, 231, 191, 247, 151, 3, 255, 25, 48, 179, 72, 165, + 181, 209, 215, 94, 146, 42, 172, 86, 170, 198, 79, 184, 56, 210, + 150, 164, 125, 182, 118, 252, 107, 226, 156, 116, 4, 241, 69, 157, + 112, 89, 100, 113, 135, 32, 134, 91, 207, 101, 230, 45, 168, 2, 27, + 96, 37, 173, 174, 176, 185, 246, 28, 70, 97, 105, 52, 64, 126, 15, + 85, 71, 163, 35, 221, 81, 175, 58, 195, 92, 249, 206, 186, 197, + 234, 38, 44, 83, 13, 110, 133, 40, 132, 9, 211, 223, 205, 244, 65, + 129, 77, 82, 106, 220, 55, 200, 108, 193, 171, 250, 36, 225, 123, + 8, 12, 189, 177, 74, 120, 136, 149, 139, 227, 99, 232, 109, 233, + 203, 213, 254, 59, 0, 29, 57, 242, 239, 183, 14, 102, 88, 208, 228, + 166, 119, 114, 248, 235, 117, 75, 10, 49, 68, 80, 180, 143, 237, + 31, 26, 219, 153, 141, 51, 159, 17, 131, 20 + ); + + // Step 1. Append Padding Bytes + $pad = 16 - (strlen($m) & 0xF); + $m.= str_repeat(chr($pad), $pad); + + $length = strlen($m); + + // Step 2. Append Checksum + $c = str_repeat(chr(0), 16); + $l = chr(0); + for ($i = 0; $i < $length; $i+= 16) { + for ($j = 0; $j < 16; $j++) { + $c[$j] = chr($s[ord($m[$i + $j] ^ $l)]); + $l = $c[$j]; + } + } + $m.= $c; + + $length+= 16; + + // Step 3. Initialize MD Buffer + $x = str_repeat(chr(0), 48); + + // Step 4. Process Message in 16-Byte Blocks + for ($i = 0; $i < $length; $i+= 16) { + for ($j = 0; $j < 16; $j++) { + $x[$j + 16] = $m[$i + $j]; + $x[$j + 32] = $x[$j + 16] ^ $x[$j]; + } + $t = chr(0); + for ($j = 0; $j < 18; $j++) { + for ($k = 0; $k < 48; $k++) { + $x[$k] = $t = $x[$k] ^ chr($s[ord($t)]); + //$t = $x[$k] = $x[$k] ^ chr($s[ord($t)]); + } + $t = chr(ord($t) + $j); + } + } + + // Step 5. Output + return substr($x, 0, 16); + } + + /** + * Pure-PHP implementation of SHA256 + * + * See {@link http://en.wikipedia.org/wiki/SHA_hash_functions#SHA-256_.28a_SHA-2_variant.29_pseudocode SHA-256 (a SHA-2 variant) pseudocode - Wikipedia}. + * + * @access private + * @param String $text + */ + function _sha256($m) + { + if (extension_loaded('suhosin')) { + return pack('H*', sha256($m)); + } + + // Initialize variables + $hash = array( + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 + ); + // Initialize table of round constants + // (first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311) + static $k = array( + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + ); + + // Pre-processing + $length = strlen($m); + // to round to nearest 56 mod 64, we'll add 64 - (length + (64 - 56)) % 64 + $m.= str_repeat(chr(0), 64 - (($length + 8) & 0x3F)); + $m[$length] = chr(0x80); + // we don't support hashing strings 512MB long + $m.= pack('N2', 0, $length << 3); + + // Process the message in successive 512-bit chunks + $chunks = str_split($m, 64); + foreach ($chunks as $chunk) { + $w = array(); + for ($i = 0; $i < 16; $i++) { + extract(unpack('Ntemp', $this->_string_shift($chunk, 4))); + $w[] = $temp; + } + + // Extend the sixteen 32-bit words into sixty-four 32-bit words + for ($i = 16; $i < 64; $i++) { + $s0 = $this->_rightRotate($w[$i - 15], 7) ^ + $this->_rightRotate($w[$i - 15], 18) ^ + $this->_rightShift( $w[$i - 15], 3); + $s1 = $this->_rightRotate($w[$i - 2], 17) ^ + $this->_rightRotate($w[$i - 2], 19) ^ + $this->_rightShift( $w[$i - 2], 10); + $w[$i] = $this->_add($w[$i - 16], $s0, $w[$i - 7], $s1); + + } + + // Initialize hash value for this chunk + list($a, $b, $c, $d, $e, $f, $g, $h) = $hash; + + // Main loop + for ($i = 0; $i < 64; $i++) { + $s0 = $this->_rightRotate($a, 2) ^ + $this->_rightRotate($a, 13) ^ + $this->_rightRotate($a, 22); + $maj = ($a & $b) ^ + ($a & $c) ^ + ($b & $c); + $t2 = $this->_add($s0, $maj); + + $s1 = $this->_rightRotate($e, 6) ^ + $this->_rightRotate($e, 11) ^ + $this->_rightRotate($e, 25); + $ch = ($e & $f) ^ + ($this->_not($e) & $g); + $t1 = $this->_add($h, $s1, $ch, $k[$i], $w[$i]); + + $h = $g; + $g = $f; + $f = $e; + $e = $this->_add($d, $t1); + $d = $c; + $c = $b; + $b = $a; + $a = $this->_add($t1, $t2); + } + + // Add this chunk's hash to result so far + $hash = array( + $this->_add($hash[0], $a), + $this->_add($hash[1], $b), + $this->_add($hash[2], $c), + $this->_add($hash[3], $d), + $this->_add($hash[4], $e), + $this->_add($hash[5], $f), + $this->_add($hash[6], $g), + $this->_add($hash[7], $h) + ); + } + + // Produce the final hash value (big-endian) + return pack('N8', $hash[0], $hash[1], $hash[2], $hash[3], $hash[4], $hash[5], $hash[6], $hash[7]); + } + + /** + * Pure-PHP implementation of SHA384 and SHA512 + * + * @access private + * @param String $text + */ + function _sha512($m) + { + if (!class_exists('Math_BigInteger')) { + require_once('Math/BigInteger.php'); + } + + static $init384, $init512, $k; + + if (!isset($k)) { + // Initialize variables + $init384 = array( // initial values for SHA384 + 'cbbb9d5dc1059ed8', '629a292a367cd507', '9159015a3070dd17', '152fecd8f70e5939', + '67332667ffc00b31', '8eb44a8768581511', 'db0c2e0d64f98fa7', '47b5481dbefa4fa4' + ); + $init512 = array( // initial values for SHA512 + '6a09e667f3bcc908', 'bb67ae8584caa73b', '3c6ef372fe94f82b', 'a54ff53a5f1d36f1', + '510e527fade682d1', '9b05688c2b3e6c1f', '1f83d9abfb41bd6b', '5be0cd19137e2179' + ); + + for ($i = 0; $i < 8; $i++) { + $init384[$i] = new Math_BigInteger($init384[$i], 16); + $init384[$i]->setPrecision(64); + $init512[$i] = new Math_BigInteger($init512[$i], 16); + $init512[$i]->setPrecision(64); + } + + // Initialize table of round constants + // (first 64 bits of the fractional parts of the cube roots of the first 80 primes 2..409) + $k = array( + '428a2f98d728ae22', '7137449123ef65cd', 'b5c0fbcfec4d3b2f', 'e9b5dba58189dbbc', + '3956c25bf348b538', '59f111f1b605d019', '923f82a4af194f9b', 'ab1c5ed5da6d8118', + 'd807aa98a3030242', '12835b0145706fbe', '243185be4ee4b28c', '550c7dc3d5ffb4e2', + '72be5d74f27b896f', '80deb1fe3b1696b1', '9bdc06a725c71235', 'c19bf174cf692694', + 'e49b69c19ef14ad2', 'efbe4786384f25e3', '0fc19dc68b8cd5b5', '240ca1cc77ac9c65', + '2de92c6f592b0275', '4a7484aa6ea6e483', '5cb0a9dcbd41fbd4', '76f988da831153b5', + '983e5152ee66dfab', 'a831c66d2db43210', 'b00327c898fb213f', 'bf597fc7beef0ee4', + 'c6e00bf33da88fc2', 'd5a79147930aa725', '06ca6351e003826f', '142929670a0e6e70', + '27b70a8546d22ffc', '2e1b21385c26c926', '4d2c6dfc5ac42aed', '53380d139d95b3df', + '650a73548baf63de', '766a0abb3c77b2a8', '81c2c92e47edaee6', '92722c851482353b', + 'a2bfe8a14cf10364', 'a81a664bbc423001', 'c24b8b70d0f89791', 'c76c51a30654be30', + 'd192e819d6ef5218', 'd69906245565a910', 'f40e35855771202a', '106aa07032bbd1b8', + '19a4c116b8d2d0c8', '1e376c085141ab53', '2748774cdf8eeb99', '34b0bcb5e19b48a8', + '391c0cb3c5c95a63', '4ed8aa4ae3418acb', '5b9cca4f7763e373', '682e6ff3d6b2b8a3', + '748f82ee5defb2fc', '78a5636f43172f60', '84c87814a1f0ab72', '8cc702081a6439ec', + '90befffa23631e28', 'a4506cebde82bde9', 'bef9a3f7b2c67915', 'c67178f2e372532b', + 'ca273eceea26619c', 'd186b8c721c0c207', 'eada7dd6cde0eb1e', 'f57d4f7fee6ed178', + '06f067aa72176fba', '0a637dc5a2c898a6', '113f9804bef90dae', '1b710b35131c471b', + '28db77f523047d84', '32caab7b40c72493', '3c9ebe0a15c9bebc', '431d67c49c100d4c', + '4cc5d4becb3e42b6', '597f299cfc657e2a', '5fcb6fab3ad6faec', '6c44198c4a475817' + ); + + for ($i = 0; $i < 80; $i++) { + $k[$i] = new Math_BigInteger($k[$i], 16); + } + } + + $hash = $this->l == 48 ? $init384 : $init512; + + // Pre-processing + $length = strlen($m); + // to round to nearest 112 mod 128, we'll add 128 - (length + (128 - 112)) % 128 + $m.= str_repeat(chr(0), 128 - (($length + 16) & 0x7F)); + $m[$length] = chr(0x80); + // we don't support hashing strings 512MB long + $m.= pack('N4', 0, 0, 0, $length << 3); + + // Process the message in successive 1024-bit chunks + $chunks = str_split($m, 128); + foreach ($chunks as $chunk) { + $w = array(); + for ($i = 0; $i < 16; $i++) { + $temp = new Math_BigInteger($this->_string_shift($chunk, 8), 256); + $temp->setPrecision(64); + $w[] = $temp; + } + + // Extend the sixteen 32-bit words into eighty 32-bit words + for ($i = 16; $i < 80; $i++) { + $temp = array( + $w[$i - 15]->bitwise_rightRotate(1), + $w[$i - 15]->bitwise_rightRotate(8), + $w[$i - 15]->bitwise_rightShift(7) + ); + $s0 = $temp[0]->bitwise_xor($temp[1]); + $s0 = $s0->bitwise_xor($temp[2]); + $temp = array( + $w[$i - 2]->bitwise_rightRotate(19), + $w[$i - 2]->bitwise_rightRotate(61), + $w[$i - 2]->bitwise_rightShift(6) + ); + $s1 = $temp[0]->bitwise_xor($temp[1]); + $s1 = $s1->bitwise_xor($temp[2]); + $w[$i] = $w[$i - 16]->copy(); + $w[$i] = $w[$i]->add($s0); + $w[$i] = $w[$i]->add($w[$i - 7]); + $w[$i] = $w[$i]->add($s1); + } + + // Initialize hash value for this chunk + $a = $hash[0]->copy(); + $b = $hash[1]->copy(); + $c = $hash[2]->copy(); + $d = $hash[3]->copy(); + $e = $hash[4]->copy(); + $f = $hash[5]->copy(); + $g = $hash[6]->copy(); + $h = $hash[7]->copy(); + + // Main loop + for ($i = 0; $i < 80; $i++) { + $temp = array( + $a->bitwise_rightRotate(28), + $a->bitwise_rightRotate(34), + $a->bitwise_rightRotate(39) + ); + $s0 = $temp[0]->bitwise_xor($temp[1]); + $s0 = $s0->bitwise_xor($temp[2]); + $temp = array( + $a->bitwise_and($b), + $a->bitwise_and($c), + $b->bitwise_and($c) + ); + $maj = $temp[0]->bitwise_xor($temp[1]); + $maj = $maj->bitwise_xor($temp[2]); + $t2 = $s0->add($maj); + + $temp = array( + $e->bitwise_rightRotate(14), + $e->bitwise_rightRotate(18), + $e->bitwise_rightRotate(41) + ); + $s1 = $temp[0]->bitwise_xor($temp[1]); + $s1 = $s1->bitwise_xor($temp[2]); + $temp = array( + $e->bitwise_and($f), + $g->bitwise_and($e->bitwise_not()) + ); + $ch = $temp[0]->bitwise_xor($temp[1]); + $t1 = $h->add($s1); + $t1 = $t1->add($ch); + $t1 = $t1->add($k[$i]); + $t1 = $t1->add($w[$i]); + + $h = $g->copy(); + $g = $f->copy(); + $f = $e->copy(); + $e = $d->add($t1); + $d = $c->copy(); + $c = $b->copy(); + $b = $a->copy(); + $a = $t1->add($t2); + } + + // Add this chunk's hash to result so far + $hash = array( + $hash[0]->add($a), + $hash[1]->add($b), + $hash[2]->add($c), + $hash[3]->add($d), + $hash[4]->add($e), + $hash[5]->add($f), + $hash[6]->add($g), + $hash[7]->add($h) + ); + } + + // Produce the final hash value (big-endian) + // (Crypt_Hash::hash() trims the output for hashes but not for HMACs. as such, we trim the output here) + $temp = $hash[0]->toBytes() . $hash[1]->toBytes() . $hash[2]->toBytes() . $hash[3]->toBytes() . + $hash[4]->toBytes() . $hash[5]->toBytes(); + if ($this->l != 48) { + $temp.= $hash[6]->toBytes() . $hash[7]->toBytes(); + } + + return $temp; + } + + /** + * Right Rotate + * + * @access private + * @param Integer $int + * @param Integer $amt + * @see _sha256() + * @return Integer + */ + function _rightRotate($int, $amt) + { + $invamt = 32 - $amt; + $mask = (1 << $invamt) - 1; + return (($int << $invamt) & 0xFFFFFFFF) | (($int >> $amt) & $mask); + } + + /** + * Right Shift + * + * @access private + * @param Integer $int + * @param Integer $amt + * @see _sha256() + * @return Integer + */ + function _rightShift($int, $amt) + { + $mask = (1 << (32 - $amt)) - 1; + return ($int >> $amt) & $mask; + } + + /** + * Not + * + * @access private + * @param Integer $int + * @see _sha256() + * @return Integer + */ + function _not($int) + { + return ~$int & 0xFFFFFFFF; + } + + /** + * Add + * + * _sha256() adds multiple unsigned 32-bit integers. Since PHP doesn't support unsigned integers and since the + * possibility of overflow exists, care has to be taken. Math_BigInteger() could be used but this should be faster. + * + * @param String $string + * @param optional Integer $index + * @return String + * @see _sha256() + * @access private + */ + function _add() + { + static $mod; + if (!isset($mod)) { + $mod = pow(2, 32); + } + + $result = 0; + $arguments = func_get_args(); + foreach ($arguments as $argument) { + $result+= $argument < 0 ? ($argument & 0x7FFFFFFF) + 0x80000000 : $argument; + } + + return fmod($result, $mod); + } + + /** + * String Shift + * + * Inspired by array_shift + * + * @param String $string + * @param optional Integer $index + * @return String + * @access private + */ + function _string_shift(&$string, $index = 1) + { + $substr = substr($string, 0, $index); + $string = substr($string, $index); + return $substr; + } } \ No newline at end of file diff --git a/plugins/OStatus/extlib/Crypt/RC4.php b/plugins/OStatus/extlib/Crypt/RC4.php index 6f82b2413..1e4d8b489 100644 --- a/plugins/OStatus/extlib/Crypt/RC4.php +++ b/plugins/OStatus/extlib/Crypt/RC4.php @@ -1,493 +1,493 @@ - - * setKey('abcdefgh'); - * - * $size = 10 * 1024; - * $plaintext = ''; - * for ($i = 0; $i < $size; $i++) { - * $plaintext.= 'a'; - * } - * - * echo $rc4->decrypt($rc4->encrypt($plaintext)); - * ?> - * - * - * LICENSE: This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, - * MA 02111-1307 USA - * - * @category Crypt - * @package Crypt_RC4 - * @author Jim Wigginton - * @copyright MMVII Jim Wigginton - * @license http://www.gnu.org/licenses/lgpl.txt - * @version $Id: RC4.php,v 1.8 2009/06/09 04:00:38 terrafrost Exp $ - * @link http://phpseclib.sourceforge.net - */ - -/**#@+ - * @access private - * @see Crypt_RC4::Crypt_RC4() - */ -/** - * Toggles the internal implementation - */ -define('CRYPT_RC4_MODE_INTERNAL', 1); -/** - * Toggles the mcrypt implementation - */ -define('CRYPT_RC4_MODE_MCRYPT', 2); -/**#@-*/ - -/**#@+ - * @access private - * @see Crypt_RC4::_crypt() - */ -define('CRYPT_RC4_ENCRYPT', 0); -define('CRYPT_RC4_DECRYPT', 1); -/**#@-*/ - -/** - * Pure-PHP implementation of RC4. - * - * @author Jim Wigginton - * @version 0.1.0 - * @access public - * @package Crypt_RC4 - */ -class Crypt_RC4 { - /** - * The Key - * - * @see Crypt_RC4::setKey() - * @var String - * @access private - */ - var $key = "\0"; - - /** - * The Key Stream for encryption - * - * If CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT, this will be equal to the mcrypt object - * - * @see Crypt_RC4::setKey() - * @var Array - * @access private - */ - var $encryptStream = false; - - /** - * The Key Stream for decryption - * - * If CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT, this will be equal to the mcrypt object - * - * @see Crypt_RC4::setKey() - * @var Array - * @access private - */ - var $decryptStream = false; - - /** - * The $i and $j indexes for encryption - * - * @see Crypt_RC4::_crypt() - * @var Integer - * @access private - */ - var $encryptIndex = 0; - - /** - * The $i and $j indexes for decryption - * - * @see Crypt_RC4::_crypt() - * @var Integer - * @access private - */ - var $decryptIndex = 0; - - /** - * MCrypt parameters - * - * @see Crypt_RC4::setMCrypt() - * @var Array - * @access private - */ - var $mcrypt = array('', ''); - - /** - * The Encryption Algorithm - * - * Only used if CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT. Only possible values are MCRYPT_RC4 or MCRYPT_ARCFOUR. - * - * @see Crypt_RC4::Crypt_RC4() - * @var Integer - * @access private - */ - var $mode; - - /** - * Default Constructor. - * - * Determines whether or not the mcrypt extension should be used. - * - * @param optional Integer $mode - * @return Crypt_RC4 - * @access public - */ - function Crypt_RC4() - { - if ( !defined('CRYPT_RC4_MODE') ) { - switch (true) { - case extension_loaded('mcrypt') && (defined('MCRYPT_ARCFOUR') || defined('MCRYPT_RC4')): - // i'd check to see if rc4 was supported, by doing in_array('arcfour', mcrypt_list_algorithms('')), - // but since that can be changed after the object has been created, there doesn't seem to be - // a lot of point... - define('CRYPT_RC4_MODE', CRYPT_RC4_MODE_MCRYPT); - break; - default: - define('CRYPT_RC4_MODE', CRYPT_RC4_MODE_INTERNAL); - } - } - - switch ( CRYPT_RC4_MODE ) { - case CRYPT_RC4_MODE_MCRYPT: - switch (true) { - case defined('MCRYPT_ARCFOUR'): - $this->mode = MCRYPT_ARCFOUR; - break; - case defined('MCRYPT_RC4'); - $this->mode = MCRYPT_RC4; - } - } - } - - /** - * Sets the key. - * - * Keys can be between 1 and 256 bytes long. If they are longer then 256 bytes, the first 256 bytes will - * be used. If no key is explicitly set, it'll be assumed to be a single null byte. - * - * @access public - * @param String $key - */ - function setKey($key) - { - $this->key = $key; - - if ( CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT ) { - return; - } - - $keyLength = strlen($key); - $keyStream = array(); - for ($i = 0; $i < 256; $i++) { - $keyStream[$i] = $i; - } - $j = 0; - for ($i = 0; $i < 256; $i++) { - $j = ($j + $keyStream[$i] + ord($key[$i % $keyLength])) & 255; - $temp = $keyStream[$i]; - $keyStream[$i] = $keyStream[$j]; - $keyStream[$j] = $temp; - } - - $this->encryptIndex = $this->decryptIndex = array(0, 0); - $this->encryptStream = $this->decryptStream = $keyStream; - } - - /** - * Dummy function. - * - * Some protocols, such as WEP, prepend an "initialization vector" to the key, effectively creating a new key [1]. - * If you need to use an initialization vector in this manner, feel free to prepend it to the key, yourself, before - * calling setKey(). - * - * [1] WEP's initialization vectors (IV's) are used in a somewhat insecure way. Since, in that protocol, - * the IV's are relatively easy to predict, an attack described by - * {@link http://www.drizzle.com/~aboba/IEEE/rc4_ksaproc.pdf Scott Fluhrer, Itsik Mantin, and Adi Shamir} - * can be used to quickly guess at the rest of the key. The following links elaborate: - * - * {@link http://www.rsa.com/rsalabs/node.asp?id=2009 http://www.rsa.com/rsalabs/node.asp?id=2009} - * {@link http://en.wikipedia.org/wiki/Related_key_attack http://en.wikipedia.org/wiki/Related_key_attack} - * - * @param String $iv - * @see Crypt_RC4::setKey() - * @access public - */ - function setIV($iv) - { - } - - /** - * Sets MCrypt parameters. (optional) - * - * If MCrypt is being used, empty strings will be used, unless otherwise specified. - * - * @link http://php.net/function.mcrypt-module-open#function.mcrypt-module-open - * @access public - * @param optional Integer $algorithm_directory - * @param optional Integer $mode_directory - */ - function setMCrypt($algorithm_directory = '', $mode_directory = '') - { - if ( CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT ) { - $this->mcrypt = array($algorithm_directory, $mode_directory); - $this->_closeMCrypt(); - } - } - - /** - * Encrypts a message. - * - * @see Crypt_RC4::_crypt() - * @access public - * @param String $plaintext - */ - function encrypt($plaintext) - { - return $this->_crypt($plaintext, CRYPT_RC4_ENCRYPT); - } - - /** - * Decrypts a message. - * - * $this->decrypt($this->encrypt($plaintext)) == $this->encrypt($this->encrypt($plaintext)). - * Atleast if the continuous buffer is disabled. - * - * @see Crypt_RC4::_crypt() - * @access public - * @param String $ciphertext - */ - function decrypt($ciphertext) - { - return $this->_crypt($ciphertext, CRYPT_RC4_DECRYPT); - } - - /** - * Encrypts or decrypts a message. - * - * @see Crypt_RC4::encrypt() - * @see Crypt_RC4::decrypt() - * @access private - * @param String $text - * @param Integer $mode - */ - function _crypt($text, $mode) - { - if ( CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT ) { - $keyStream = $mode == CRYPT_RC4_ENCRYPT ? 'encryptStream' : 'decryptStream'; - - if ($this->$keyStream === false) { - $this->$keyStream = mcrypt_module_open($this->mode, $this->mcrypt[0], MCRYPT_MODE_STREAM, $this->mcrypt[1]); - mcrypt_generic_init($this->$keyStream, $this->key, ''); - } else if (!$this->continuousBuffer) { - mcrypt_generic_init($this->$keyStream, $this->key, ''); - } - $newText = mcrypt_generic($this->$keyStream, $text); - if (!$this->continuousBuffer) { - mcrypt_generic_deinit($this->$keyStream); - } - - return $newText; - } - - if ($this->encryptStream === false) { - $this->setKey($this->key); - } - - switch ($mode) { - case CRYPT_RC4_ENCRYPT: - $keyStream = $this->encryptStream; - list($i, $j) = $this->encryptIndex; - break; - case CRYPT_RC4_DECRYPT: - $keyStream = $this->decryptStream; - list($i, $j) = $this->decryptIndex; - } - - $newText = ''; - for ($k = 0; $k < strlen($text); $k++) { - $i = ($i + 1) & 255; - $j = ($j + $keyStream[$i]) & 255; - $temp = $keyStream[$i]; - $keyStream[$i] = $keyStream[$j]; - $keyStream[$j] = $temp; - $temp = $keyStream[($keyStream[$i] + $keyStream[$j]) & 255]; - $newText.= chr(ord($text[$k]) ^ $temp); - } - - if ($this->continuousBuffer) { - switch ($mode) { - case CRYPT_RC4_ENCRYPT: - $this->encryptStream = $keyStream; - $this->encryptIndex = array($i, $j); - break; - case CRYPT_RC4_DECRYPT: - $this->decryptStream = $keyStream; - $this->decryptIndex = array($i, $j); - } - } - - return $newText; - } - - /** - * Treat consecutive "packets" as if they are a continuous buffer. - * - * Say you have a 16-byte plaintext $plaintext. Using the default behavior, the two following code snippets - * will yield different outputs: - * - * - * echo $rc4->encrypt(substr($plaintext, 0, 8)); - * echo $rc4->encrypt(substr($plaintext, 8, 8)); - * - * - * echo $rc4->encrypt($plaintext); - * - * - * The solution is to enable the continuous buffer. Although this will resolve the above discrepancy, it creates - * another, as demonstrated with the following: - * - * - * $rc4->encrypt(substr($plaintext, 0, 8)); - * echo $rc4->decrypt($des->encrypt(substr($plaintext, 8, 8))); - * - * - * echo $rc4->decrypt($des->encrypt(substr($plaintext, 8, 8))); - * - * - * With the continuous buffer disabled, these would yield the same output. With it enabled, they yield different - * outputs. The reason is due to the fact that the initialization vector's change after every encryption / - * decryption round when the continuous buffer is enabled. When it's disabled, they remain constant. - * - * Put another way, when the continuous buffer is enabled, the state of the Crypt_DES() object changes after each - * encryption / decryption round, whereas otherwise, it'd remain constant. For this reason, it's recommended that - * continuous buffers not be used. They do offer better security and are, in fact, sometimes required (SSH uses them), - * however, they are also less intuitive and more likely to cause you problems. - * - * @see Crypt_RC4::disableContinuousBuffer() - * @access public - */ - function enableContinuousBuffer() - { - $this->continuousBuffer = true; - } - - /** - * Treat consecutive packets as if they are a discontinuous buffer. - * - * The default behavior. - * - * @see Crypt_RC4::enableContinuousBuffer() - * @access public - */ - function disableContinuousBuffer() - { - if ( CRYPT_RC4_MODE == CRYPT_RC4_MODE_INTERNAL ) { - $this->encryptIndex = $this->decryptIndex = array(0, 0); - $this->setKey($this->key); - } - - $this->continuousBuffer = false; - } - - /** - * Dummy function. - * - * Since RC4 is a stream cipher and not a block cipher, no padding is necessary. The only reason this function is - * included is so that you can switch between a block cipher and a stream cipher transparently. - * - * @see Crypt_RC4::disablePadding() - * @access public - */ - function enablePadding() - { - } - - /** - * Dummy function. - * - * @see Crypt_RC4::enablePadding() - * @access public - */ - function disablePadding() - { - } - - /** - * Class destructor. - * - * Will be called, automatically, if you're using PHP5. If you're using PHP4, call it yourself. Only really - * needs to be called if mcrypt is being used. - * - * @access public - */ - function __destruct() - { - if ( CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT ) { - $this->_closeMCrypt(); - } - } - - /** - * Properly close the MCrypt objects. - * - * @access prviate - */ - function _closeMCrypt() - { - if ( $this->encryptStream !== false ) { - if ( $this->continuousBuffer ) { - mcrypt_generic_deinit($this->encryptStream); - } - - mcrypt_module_close($this->encryptStream); - - $this->encryptStream = false; - } - - if ( $this->decryptStream !== false ) { - if ( $this->continuousBuffer ) { - mcrypt_generic_deinit($this->decryptStream); - } - - mcrypt_module_close($this->decryptStream); - - $this->decryptStream = false; - } - } + + * setKey('abcdefgh'); + * + * $size = 10 * 1024; + * $plaintext = ''; + * for ($i = 0; $i < $size; $i++) { + * $plaintext.= 'a'; + * } + * + * echo $rc4->decrypt($rc4->encrypt($plaintext)); + * ?> + * + * + * LICENSE: This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * @category Crypt + * @package Crypt_RC4 + * @author Jim Wigginton + * @copyright MMVII Jim Wigginton + * @license http://www.gnu.org/licenses/lgpl.txt + * @version $Id: RC4.php,v 1.8 2009/06/09 04:00:38 terrafrost Exp $ + * @link http://phpseclib.sourceforge.net + */ + +/**#@+ + * @access private + * @see Crypt_RC4::Crypt_RC4() + */ +/** + * Toggles the internal implementation + */ +define('CRYPT_RC4_MODE_INTERNAL', 1); +/** + * Toggles the mcrypt implementation + */ +define('CRYPT_RC4_MODE_MCRYPT', 2); +/**#@-*/ + +/**#@+ + * @access private + * @see Crypt_RC4::_crypt() + */ +define('CRYPT_RC4_ENCRYPT', 0); +define('CRYPT_RC4_DECRYPT', 1); +/**#@-*/ + +/** + * Pure-PHP implementation of RC4. + * + * @author Jim Wigginton + * @version 0.1.0 + * @access public + * @package Crypt_RC4 + */ +class Crypt_RC4 { + /** + * The Key + * + * @see Crypt_RC4::setKey() + * @var String + * @access private + */ + var $key = "\0"; + + /** + * The Key Stream for encryption + * + * If CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT, this will be equal to the mcrypt object + * + * @see Crypt_RC4::setKey() + * @var Array + * @access private + */ + var $encryptStream = false; + + /** + * The Key Stream for decryption + * + * If CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT, this will be equal to the mcrypt object + * + * @see Crypt_RC4::setKey() + * @var Array + * @access private + */ + var $decryptStream = false; + + /** + * The $i and $j indexes for encryption + * + * @see Crypt_RC4::_crypt() + * @var Integer + * @access private + */ + var $encryptIndex = 0; + + /** + * The $i and $j indexes for decryption + * + * @see Crypt_RC4::_crypt() + * @var Integer + * @access private + */ + var $decryptIndex = 0; + + /** + * MCrypt parameters + * + * @see Crypt_RC4::setMCrypt() + * @var Array + * @access private + */ + var $mcrypt = array('', ''); + + /** + * The Encryption Algorithm + * + * Only used if CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT. Only possible values are MCRYPT_RC4 or MCRYPT_ARCFOUR. + * + * @see Crypt_RC4::Crypt_RC4() + * @var Integer + * @access private + */ + var $mode; + + /** + * Default Constructor. + * + * Determines whether or not the mcrypt extension should be used. + * + * @param optional Integer $mode + * @return Crypt_RC4 + * @access public + */ + function Crypt_RC4() + { + if ( !defined('CRYPT_RC4_MODE') ) { + switch (true) { + case extension_loaded('mcrypt') && (defined('MCRYPT_ARCFOUR') || defined('MCRYPT_RC4')): + // i'd check to see if rc4 was supported, by doing in_array('arcfour', mcrypt_list_algorithms('')), + // but since that can be changed after the object has been created, there doesn't seem to be + // a lot of point... + define('CRYPT_RC4_MODE', CRYPT_RC4_MODE_MCRYPT); + break; + default: + define('CRYPT_RC4_MODE', CRYPT_RC4_MODE_INTERNAL); + } + } + + switch ( CRYPT_RC4_MODE ) { + case CRYPT_RC4_MODE_MCRYPT: + switch (true) { + case defined('MCRYPT_ARCFOUR'): + $this->mode = MCRYPT_ARCFOUR; + break; + case defined('MCRYPT_RC4'); + $this->mode = MCRYPT_RC4; + } + } + } + + /** + * Sets the key. + * + * Keys can be between 1 and 256 bytes long. If they are longer then 256 bytes, the first 256 bytes will + * be used. If no key is explicitly set, it'll be assumed to be a single null byte. + * + * @access public + * @param String $key + */ + function setKey($key) + { + $this->key = $key; + + if ( CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT ) { + return; + } + + $keyLength = strlen($key); + $keyStream = array(); + for ($i = 0; $i < 256; $i++) { + $keyStream[$i] = $i; + } + $j = 0; + for ($i = 0; $i < 256; $i++) { + $j = ($j + $keyStream[$i] + ord($key[$i % $keyLength])) & 255; + $temp = $keyStream[$i]; + $keyStream[$i] = $keyStream[$j]; + $keyStream[$j] = $temp; + } + + $this->encryptIndex = $this->decryptIndex = array(0, 0); + $this->encryptStream = $this->decryptStream = $keyStream; + } + + /** + * Dummy function. + * + * Some protocols, such as WEP, prepend an "initialization vector" to the key, effectively creating a new key [1]. + * If you need to use an initialization vector in this manner, feel free to prepend it to the key, yourself, before + * calling setKey(). + * + * [1] WEP's initialization vectors (IV's) are used in a somewhat insecure way. Since, in that protocol, + * the IV's are relatively easy to predict, an attack described by + * {@link http://www.drizzle.com/~aboba/IEEE/rc4_ksaproc.pdf Scott Fluhrer, Itsik Mantin, and Adi Shamir} + * can be used to quickly guess at the rest of the key. The following links elaborate: + * + * {@link http://www.rsa.com/rsalabs/node.asp?id=2009 http://www.rsa.com/rsalabs/node.asp?id=2009} + * {@link http://en.wikipedia.org/wiki/Related_key_attack http://en.wikipedia.org/wiki/Related_key_attack} + * + * @param String $iv + * @see Crypt_RC4::setKey() + * @access public + */ + function setIV($iv) + { + } + + /** + * Sets MCrypt parameters. (optional) + * + * If MCrypt is being used, empty strings will be used, unless otherwise specified. + * + * @link http://php.net/function.mcrypt-module-open#function.mcrypt-module-open + * @access public + * @param optional Integer $algorithm_directory + * @param optional Integer $mode_directory + */ + function setMCrypt($algorithm_directory = '', $mode_directory = '') + { + if ( CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT ) { + $this->mcrypt = array($algorithm_directory, $mode_directory); + $this->_closeMCrypt(); + } + } + + /** + * Encrypts a message. + * + * @see Crypt_RC4::_crypt() + * @access public + * @param String $plaintext + */ + function encrypt($plaintext) + { + return $this->_crypt($plaintext, CRYPT_RC4_ENCRYPT); + } + + /** + * Decrypts a message. + * + * $this->decrypt($this->encrypt($plaintext)) == $this->encrypt($this->encrypt($plaintext)). + * Atleast if the continuous buffer is disabled. + * + * @see Crypt_RC4::_crypt() + * @access public + * @param String $ciphertext + */ + function decrypt($ciphertext) + { + return $this->_crypt($ciphertext, CRYPT_RC4_DECRYPT); + } + + /** + * Encrypts or decrypts a message. + * + * @see Crypt_RC4::encrypt() + * @see Crypt_RC4::decrypt() + * @access private + * @param String $text + * @param Integer $mode + */ + function _crypt($text, $mode) + { + if ( CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT ) { + $keyStream = $mode == CRYPT_RC4_ENCRYPT ? 'encryptStream' : 'decryptStream'; + + if ($this->$keyStream === false) { + $this->$keyStream = mcrypt_module_open($this->mode, $this->mcrypt[0], MCRYPT_MODE_STREAM, $this->mcrypt[1]); + mcrypt_generic_init($this->$keyStream, $this->key, ''); + } else if (!$this->continuousBuffer) { + mcrypt_generic_init($this->$keyStream, $this->key, ''); + } + $newText = mcrypt_generic($this->$keyStream, $text); + if (!$this->continuousBuffer) { + mcrypt_generic_deinit($this->$keyStream); + } + + return $newText; + } + + if ($this->encryptStream === false) { + $this->setKey($this->key); + } + + switch ($mode) { + case CRYPT_RC4_ENCRYPT: + $keyStream = $this->encryptStream; + list($i, $j) = $this->encryptIndex; + break; + case CRYPT_RC4_DECRYPT: + $keyStream = $this->decryptStream; + list($i, $j) = $this->decryptIndex; + } + + $newText = ''; + for ($k = 0; $k < strlen($text); $k++) { + $i = ($i + 1) & 255; + $j = ($j + $keyStream[$i]) & 255; + $temp = $keyStream[$i]; + $keyStream[$i] = $keyStream[$j]; + $keyStream[$j] = $temp; + $temp = $keyStream[($keyStream[$i] + $keyStream[$j]) & 255]; + $newText.= chr(ord($text[$k]) ^ $temp); + } + + if ($this->continuousBuffer) { + switch ($mode) { + case CRYPT_RC4_ENCRYPT: + $this->encryptStream = $keyStream; + $this->encryptIndex = array($i, $j); + break; + case CRYPT_RC4_DECRYPT: + $this->decryptStream = $keyStream; + $this->decryptIndex = array($i, $j); + } + } + + return $newText; + } + + /** + * Treat consecutive "packets" as if they are a continuous buffer. + * + * Say you have a 16-byte plaintext $plaintext. Using the default behavior, the two following code snippets + * will yield different outputs: + * + * + * echo $rc4->encrypt(substr($plaintext, 0, 8)); + * echo $rc4->encrypt(substr($plaintext, 8, 8)); + * + * + * echo $rc4->encrypt($plaintext); + * + * + * The solution is to enable the continuous buffer. Although this will resolve the above discrepancy, it creates + * another, as demonstrated with the following: + * + * + * $rc4->encrypt(substr($plaintext, 0, 8)); + * echo $rc4->decrypt($des->encrypt(substr($plaintext, 8, 8))); + * + * + * echo $rc4->decrypt($des->encrypt(substr($plaintext, 8, 8))); + * + * + * With the continuous buffer disabled, these would yield the same output. With it enabled, they yield different + * outputs. The reason is due to the fact that the initialization vector's change after every encryption / + * decryption round when the continuous buffer is enabled. When it's disabled, they remain constant. + * + * Put another way, when the continuous buffer is enabled, the state of the Crypt_DES() object changes after each + * encryption / decryption round, whereas otherwise, it'd remain constant. For this reason, it's recommended that + * continuous buffers not be used. They do offer better security and are, in fact, sometimes required (SSH uses them), + * however, they are also less intuitive and more likely to cause you problems. + * + * @see Crypt_RC4::disableContinuousBuffer() + * @access public + */ + function enableContinuousBuffer() + { + $this->continuousBuffer = true; + } + + /** + * Treat consecutive packets as if they are a discontinuous buffer. + * + * The default behavior. + * + * @see Crypt_RC4::enableContinuousBuffer() + * @access public + */ + function disableContinuousBuffer() + { + if ( CRYPT_RC4_MODE == CRYPT_RC4_MODE_INTERNAL ) { + $this->encryptIndex = $this->decryptIndex = array(0, 0); + $this->setKey($this->key); + } + + $this->continuousBuffer = false; + } + + /** + * Dummy function. + * + * Since RC4 is a stream cipher and not a block cipher, no padding is necessary. The only reason this function is + * included is so that you can switch between a block cipher and a stream cipher transparently. + * + * @see Crypt_RC4::disablePadding() + * @access public + */ + function enablePadding() + { + } + + /** + * Dummy function. + * + * @see Crypt_RC4::enablePadding() + * @access public + */ + function disablePadding() + { + } + + /** + * Class destructor. + * + * Will be called, automatically, if you're using PHP5. If you're using PHP4, call it yourself. Only really + * needs to be called if mcrypt is being used. + * + * @access public + */ + function __destruct() + { + if ( CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT ) { + $this->_closeMCrypt(); + } + } + + /** + * Properly close the MCrypt objects. + * + * @access prviate + */ + function _closeMCrypt() + { + if ( $this->encryptStream !== false ) { + if ( $this->continuousBuffer ) { + mcrypt_generic_deinit($this->encryptStream); + } + + mcrypt_module_close($this->encryptStream); + + $this->encryptStream = false; + } + + if ( $this->decryptStream !== false ) { + if ( $this->continuousBuffer ) { + mcrypt_generic_deinit($this->decryptStream); + } + + mcrypt_module_close($this->decryptStream); + + $this->decryptStream = false; + } + } } \ No newline at end of file diff --git a/plugins/OStatus/extlib/Crypt/RSA.php b/plugins/OStatus/extlib/Crypt/RSA.php index b9a4e23eb..f0a75962c 100644 --- a/plugins/OStatus/extlib/Crypt/RSA.php +++ b/plugins/OStatus/extlib/Crypt/RSA.php @@ -1,1929 +1,2108 @@ - - * createKey()); - * - * $plaintext = 'terrafrost'; - * - * $rsa->loadKey($privatekey); - * $ciphertext = $rsa->encrypt($plaintext); - * - * $rsa->loadKey($publickey); - * echo $rsa->decrypt($ciphertext); - * ?> - * - * - * Here's an example of how to create signatures and verify signatures with this library: - * - * createKey()); - * - * $plaintext = 'terrafrost'; - * - * $rsa->loadKey($privatekey); - * $signature = $rsa->sign($plaintext); - * - * $rsa->loadKey($publickey); - * echo $rsa->verify($plaintext, $signature) ? 'verified' : 'unverified'; - * ?> - * - * - * LICENSE: This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, - * MA 02111-1307 USA - * - * @category Crypt - * @package Crypt_RSA - * @author Jim Wigginton - * @copyright MMIX Jim Wigginton - * @license http://www.gnu.org/licenses/lgpl.txt - * @version $Id: RSA.php,v 1.3 2009/12/04 21:05:32 terrafrost Exp $ - * @link http://phpseclib.sourceforge.net - */ - -/** - * Include Math_BigInteger - */ -require_once('Math/BigInteger.php'); - -/** - * Include Crypt_Random - */ -require_once('Crypt/Random.php'); - -/** - * Include Crypt_Hash - */ -require_once('Crypt/Hash.php'); - -/**#@+ - * @access public - * @see Crypt_RSA::encrypt() - * @see Crypt_RSA::decrypt() - */ -/** - * Use {@link http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding Optimal Asymmetric Encryption Padding} - * (OAEP) for encryption / decryption. - * - * Uses sha1 by default. - * - * @see Crypt_RSA::setHash() - * @see Crypt_RSA::setMGFHash() - */ -define('CRYPT_RSA_ENCRYPTION_OAEP', 1); -/** - * Use PKCS#1 padding. - * - * Although CRYPT_RSA_ENCRYPTION_OAEP offers more security, including PKCS#1 padding is necessary for purposes of backwards - * compatability with protocols (like SSH-1) written before OAEP's introduction. - */ -define('CRYPT_RSA_ENCRYPTION_PKCS1', 2); -/**#@-*/ - -/**#@+ - * @access public - * @see Crypt_RSA::sign() - * @see Crypt_RSA::verify() - * @see Crypt_RSA::setHash() - */ -/** - * Use the Probabilistic Signature Scheme for signing - * - * Uses sha1 by default. - * - * @see Crypt_RSA::setSaltLength() - * @see Crypt_RSA::setMGFHash() - */ -define('CRYPT_RSA_SIGNATURE_PSS', 1); -/** - * Use the PKCS#1 scheme by default. - * - * Although CRYPT_RSA_SIGNATURE_PSS offers more security, including PKCS#1 signing is necessary for purposes of backwards - * compatability with protocols (like SSH-2) written before PSS's introduction. - */ -define('CRYPT_RSA_SIGNATURE_PKCS1', 2); -/**#@-*/ - -/**#@+ - * @access private - * @see Crypt_RSA::createKey() - */ -/** - * ASN1 Integer - */ -define('CRYPT_RSA_ASN1_INTEGER', 2); -/** - * ASN1 Sequence (with the constucted bit set) - */ -define('CRYPT_RSA_ASN1_SEQUENCE', 48); -/**#@-*/ - -/**#@+ - * @access private - * @see Crypt_RSA::Crypt_RSA() - */ -/** - * To use the pure-PHP implementation - */ -define('CRYPT_RSA_MODE_INTERNAL', 1); -/** - * To use the OpenSSL library - * - * (if enabled; otherwise, the internal implementation will be used) - */ -define('CRYPT_RSA_MODE_OPENSSL', 2); -/**#@-*/ - -/**#@+ - * @access public - * @see Crypt_RSA::createKey() - * @see Crypt_RSA::setPrivateKeyFormat() - */ -/** - * PKCS#1 formatted private key - * - * Used by OpenSSH - */ -define('CRYPT_RSA_PRIVATE_FORMAT_PKCS1', 0); -/**#@-*/ - -/**#@+ - * @access public - * @see Crypt_RSA::createKey() - * @see Crypt_RSA::setPublicKeyFormat() - */ -/** - * Raw public key - * - * An array containing two Math_BigInteger objects. - * - * The exponent can be indexed with any of the following: - * - * 0, e, exponent, publicExponent - * - * The modulus can be indexed with any of the following: - * - * 1, n, modulo, modulus - */ -define('CRYPT_RSA_PUBLIC_FORMAT_RAW', 1); -/** - * PKCS#1 formatted public key - */ -define('CRYPT_RSA_PUBLIC_FORMAT_PKCS1', 2); -/** - * OpenSSH formatted public key - * - * Place in $HOME/.ssh/authorized_keys - */ -define('CRYPT_RSA_PUBLIC_FORMAT_OPENSSH', 3); -/**#@-*/ - -/** - * Pure-PHP PKCS#1 compliant implementation of RSA. - * - * @author Jim Wigginton - * @version 0.1.0 - * @access public - * @package Crypt_RSA - */ -class Crypt_RSA { - /** - * Precomputed Zero - * - * @var Array - * @access private - */ - var $zero; - - /** - * Precomputed One - * - * @var Array - * @access private - */ - var $one; - - /** - * Private Key Format - * - * @var Integer - * @access private - */ - var $privateKeyFormat = CRYPT_RSA_PRIVATE_FORMAT_PKCS1; - - /** - * Public Key Format - * - * @var Integer - * @access public - */ - var $publicKeyFormat = CRYPT_RSA_PUBLIC_FORMAT_PKCS1; - - /** - * Modulus (ie. n) - * - * @var Math_BigInteger - * @access private - */ - var $modulus; - - /** - * Modulus length - * - * @var Math_BigInteger - * @access private - */ - var $k; - - /** - * Exponent (ie. e or d) - * - * @var Math_BigInteger - * @access private - */ - var $exponent; - - /** - * Primes for Chinese Remainder Theorem (ie. p and q) - * - * @var Array - * @access private - */ - var $primes; - - /** - * Exponents for Chinese Remainder Theorem (ie. dP and dQ) - * - * @var Array - * @access private - */ - var $exponents; - - /** - * Coefficients for Chinese Remainder Theorem (ie. qInv) - * - * @var Array - * @access private - */ - var $coefficients; - - /** - * Hash name - * - * @var String - * @access private - */ - var $hashName; - - /** - * Hash function - * - * @var Crypt_Hash - * @access private - */ - var $hash; - - /** - * Length of hash function output - * - * @var Integer - * @access private - */ - var $hLen; - - /** - * Length of salt - * - * @var Integer - * @access private - */ - var $sLen; - - /** - * Hash function for the Mask Generation Function - * - * @var Crypt_Hash - * @access private - */ - var $mgfHash; - - /** - * Encryption mode - * - * @var Integer - * @access private - */ - var $encryptionMode = CRYPT_RSA_ENCRYPTION_OAEP; - - /** - * Signature mode - * - * @var Integer - * @access private - */ - var $signatureMode = CRYPT_RSA_SIGNATURE_PSS; - - /** - * Public Exponent - * - * @var Mixed - * @access private - */ - var $publicExponent = false; - - /** - * Password - * - * @var String - * @access private - */ - var $password = ''; - - /** - * The constructor - * - * If you want to make use of the openssl extension, you'll need to set the mode manually, yourself. The reason - * Crypt_RSA doesn't do it is because OpenSSL doesn't fail gracefully. openssl_pkey_new(), in particular, requires - * openssl.cnf be present somewhere and, unfortunately, the only real way to find out is too late. - * - * @return Crypt_RSA - * @access public - */ - function Crypt_RSA() - { - if ( !defined('CRYPT_RSA_MODE') ) { - switch (true) { - //case extension_loaded('openssl') && version_compare(PHP_VERSION, '4.2.0', '>='): - // define('CRYPT_RSA_MODE', CRYPT_RSA_MODE_OPENSSL); - // break; - default: - define('CRYPT_RSA_MODE', CRYPT_RSA_MODE_INTERNAL); - } - } - - $this->zero = new Math_BigInteger(); - $this->one = new Math_BigInteger(1); - - $this->hash = new Crypt_Hash('sha1'); - $this->hLen = $this->hash->getLength(); - $this->hashName = 'sha1'; - $this->mgfHash = new Crypt_Hash('sha1'); - } - - /** - * Create public / private key pair - * - * Returns an array with the following three elements: - * - 'privatekey': The private key. - * - 'publickey': The public key. - * - 'partialkey': A partially computed key (if the execution time exceeded $timeout). - * Will need to be passed back to Crypt_RSA::createKey() as the third parameter for further processing. - * - * @access public - * @param optional Integer $bits - * @param optional Integer $timeout - * @param optional Math_BigInteger $p - */ - function createKey($bits = 1024, $timeout = false, $primes = array()) - { - if ( CRYPT_RSA_MODE == CRYPT_RSA_MODE_OPENSSL ) { - $rsa = openssl_pkey_new(array('private_key_bits' => $bits)); - openssl_pkey_export($rsa, $privatekey); - $publickey = openssl_pkey_get_details($rsa); - $publickey = $publickey['key']; - - if ($this->privateKeyFormat != CRYPT_RSA_PRIVATE_FORMAT_PKCS1) { - $privatekey = call_user_func_array(array($this, '_convertPrivateKey'), array_values($this->_parseKey($privatekey, CRYPT_RSA_PRIVATE_FORMAT_PKCS1))); - $publickey = call_user_func_array(array($this, '_convertPublicKey'), array_values($this->_parseKey($publickey, CRYPT_RSA_PUBLIC_FORMAT_PKCS1))); - } - - return array( - 'privatekey' => $privatekey, - 'publickey' => $publickey, - 'partialkey' => false - ); - } - - static $e; - if (!isset($e)) { - if (!defined('CRYPT_RSA_EXPONENT')) { - // http://en.wikipedia.org/wiki/65537_%28number%29 - define('CRYPT_RSA_EXPONENT', '65537'); - } - if (!defined('CRYPT_RSA_COMMENT')) { - define('CRYPT_RSA_COMMENT', 'phpseclib-generated-key'); - } - // per , this number ought not result in primes smaller - // than 256 bits. - if (!defined('CRYPT_RSA_SMALLEST_PRIME')) { - define('CRYPT_RSA_SMALLEST_PRIME', 4096); - } - - $e = new Math_BigInteger(CRYPT_RSA_EXPONENT); - } - - extract($this->_generateMinMax($bits)); - $absoluteMin = $min; - $temp = $bits >> 1; - if ($temp > CRYPT_RSA_SMALLEST_PRIME) { - $num_primes = floor($bits / CRYPT_RSA_SMALLEST_PRIME); - $temp = CRYPT_RSA_SMALLEST_PRIME; - } else { - $num_primes = 2; - } - extract($this->_generateMinMax($temp + $bits % $temp)); - $finalMax = $max; - extract($this->_generateMinMax($temp)); - - $exponents = $coefficients = array(); - $generator = new Math_BigInteger(); - $generator->setRandomGenerator('crypt_random'); - - $n = $this->one->copy(); - $lcm = array( - 'top' => $this->one->copy(), - 'bottom' => false - ); - - $start = time(); - $i0 = count($primes) + 1; - - do { - for ($i = $i0; $i <= $num_primes; $i++) { - if ($timeout !== false) { - $timeout-= time() - $start; - $start = time(); - if ($timeout <= 0) { - return array( - 'privatekey' => '', - 'publickey' => '', - 'partialkey' => $primes - ); - } - } - if ($i == $num_primes) { - list($min, $temp) = $absoluteMin->divide($n); - if (!$temp->equals($this->zero)) { - $min = $min->add($this->one); // ie. ceil() - } - $primes[$i] = $generator->randomPrime($min, $finalMax, $timeout); - } else { - $primes[$i] = $generator->randomPrime($min, $max, $timeout); - } - - if ($primes[$i] === false) { // if we've reached the timeout - return array( - 'privatekey' => '', - 'publickey' => '', - 'partialkey' => array_slice($primes, 0, $i - 1) - ); - } - - // the first coefficient is calculated differently from the rest - // ie. instead of being $primes[1]->modInverse($primes[2]), it's $primes[2]->modInverse($primes[1]) - if ($i > 2) { - $coefficients[$i] = $n->modInverse($primes[$i]); - } - - $n = $n->multiply($primes[$i]); - - $temp = $primes[$i]->subtract($this->one); - - // textbook RSA implementations use Euler's totient function instead of the least common multiple. - // see http://en.wikipedia.org/wiki/Euler%27s_totient_function - $lcm['top'] = $lcm['top']->multiply($temp); - $lcm['bottom'] = $lcm['bottom'] === false ? $temp : $lcm['bottom']->gcd($temp); - - $exponents[$i] = $e->modInverse($temp); - } - - list($lcm) = $lcm['top']->divide($lcm['bottom']); - $gcd = $lcm->gcd($e); - $i0 = 1; - } while (!$gcd->equals($this->one)); - - $d = $e->modInverse($lcm); - - $coefficients[2] = $primes[2]->modInverse($primes[1]); - - // from : - // RSAPrivateKey ::= SEQUENCE { - // version Version, - // modulus INTEGER, -- n - // publicExponent INTEGER, -- e - // privateExponent INTEGER, -- d - // prime1 INTEGER, -- p - // prime2 INTEGER, -- q - // exponent1 INTEGER, -- d mod (p-1) - // exponent2 INTEGER, -- d mod (q-1) - // coefficient INTEGER, -- (inverse of q) mod p - // otherPrimeInfos OtherPrimeInfos OPTIONAL - // } - - return array( - 'privatekey' => $this->_convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients), - 'publickey' => $this->_convertPublicKey($n, $e), - 'partialkey' => false - ); - } - - /** - * Convert a private key to the appropriate format. - * - * @access private - * @see setPrivateKeyFormat() - * @param String $RSAPrivateKey - * @return String - */ - function _convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients) - { - $num_primes = count($primes); - - $raw = array( - 'version' => $num_primes == 2 ? chr(0) : chr(1), // two-prime vs. multi - 'modulus' => $n->toBytes(true), - 'publicExponent' => $e->toBytes(true), - 'privateExponent' => $d->toBytes(true), - 'prime1' => $primes[1]->toBytes(true), - 'prime2' => $primes[2]->toBytes(true), - 'exponent1' => $exponents[1]->toBytes(true), - 'exponent2' => $exponents[2]->toBytes(true), - 'coefficient' => $coefficients[2]->toBytes(true) - ); - - // if the format in question does not support multi-prime rsa and multi-prime rsa was used, - // call _convertPublicKey() instead. - switch ($this->privateKeyFormat) { - default: // eg. CRYPT_RSA_PRIVATE_FORMAT_PKCS1 - $components = array(); - foreach ($raw as $name => $value) { - $components[$name] = pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($value)), $value); - } - - $RSAPrivateKey = implode('', $components); - - if ($num_primes > 2) { - $OtherPrimeInfos = ''; - for ($i = 3; $i <= $num_primes; $i++) { - // OtherPrimeInfos ::= SEQUENCE SIZE(1..MAX) OF OtherPrimeInfo - // - // OtherPrimeInfo ::= SEQUENCE { - // prime INTEGER, -- ri - // exponent INTEGER, -- di - // coefficient INTEGER -- ti - // } - $OtherPrimeInfo = pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($primes[$i]->toBytes(true))), $primes[$i]->toBytes(true)); - $OtherPrimeInfo.= pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($exponents[$i]->toBytes(true))), $exponents[$i]->toBytes(true)); - $OtherPrimeInfo.= pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($coefficients[$i]->toBytes(true))), $coefficients[$i]->toBytes(true)); - $OtherPrimeInfos.= pack('Ca*a*', CRYPT_RSA_ASN1_SEQUENCE, $this->_encodeLength(strlen($OtherPrimeInfo)), $OtherPrimeInfo); - } - $RSAPrivateKey.= pack('Ca*a*', CRYPT_RSA_ASN1_SEQUENCE, $this->_encodeLength(strlen($OtherPrimeInfos)), $OtherPrimeInfos); - } - - $RSAPrivateKey = pack('Ca*a*', CRYPT_RSA_ASN1_SEQUENCE, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey); - - if (!empty($this->password)) { - $iv = $this->_random(8); - $symkey = pack('H*', md5($this->password . $iv)); // symkey is short for symmetric key - $symkey.= substr(pack('H*', md5($symkey . $this->password . $iv)), 0, 8); - if (!class_exists('Crypt_TripleDES')) { - require_once('Crypt/TripleDES.php'); - } - $des = new Crypt_TripleDES(); - $des->setKey($symkey); - $des->setIV($iv); - $iv = strtoupper(bin2hex($iv)); - $RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n" . - "Proc-Type: 4,ENCRYPTED\r\n" . - "DEK-Info: DES-EDE3-CBC,$iv\r\n" . - "\r\n" . - chunk_split(base64_encode($des->encrypt($RSAPrivateKey))) . - '-----END RSA PRIVATE KEY-----'; - } else { - $RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n" . - chunk_split(base64_encode($RSAPrivateKey)) . - '-----END RSA PRIVATE KEY-----'; - } - - return $RSAPrivateKey; - } - } - - /** - * Convert a public key to the appropriate format - * - * @access private - * @see setPublicKeyFormat() - * @param String $RSAPrivateKey - * @return String - */ - function _convertPublicKey($n, $e) - { - $modulus = $n->toBytes(true); - $publicExponent = $e->toBytes(true); - - switch ($this->publicKeyFormat) { - case CRYPT_RSA_PUBLIC_FORMAT_RAW: - return array('e' => $e->copy(), 'n' => $n->copy()); - case CRYPT_RSA_PUBLIC_FORMAT_OPENSSH: - // from : - // string "ssh-rsa" - // mpint e - // mpint n - $RSAPublicKey = pack('Na*Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($publicExponent), $publicExponent, strlen($modulus), $modulus); - $RSAPublicKey = 'ssh-rsa ' . base64_encode($RSAPublicKey) . ' ' . CRYPT_RSA_COMMENT; - - return $RSAPublicKey; - default: // eg. CRYPT_RSA_PUBLIC_FORMAT_PKCS1 - // from : - // RSAPublicKey ::= SEQUENCE { - // modulus INTEGER, -- n - // publicExponent INTEGER -- e - // } - $components = array( - 'modulus' => pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($modulus)), $modulus), - 'publicExponent' => pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($publicExponent)), $publicExponent) - ); - - $RSAPublicKey = pack('Ca*a*a*', - CRYPT_RSA_ASN1_SEQUENCE, $this->_encodeLength(strlen($components['modulus']) + strlen($components['publicExponent'])), - $components['modulus'], $components['publicExponent'] - ); - - $RSAPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" . - chunk_split(base64_encode($RSAPublicKey)) . - '-----END PUBLIC KEY-----'; - - return $RSAPublicKey; - } - } - - /** - * Break a public or private key down into its constituant components - * - * @access private - * @see _convertPublicKey() - * @see _convertPrivateKey() - * @param String $key - * @param Integer $type - * @return Array - */ - function _parseKey($key, $type) - { - switch ($type) { - case CRYPT_RSA_PUBLIC_FORMAT_RAW: - if (!is_array($key)) { - return false; - } - $components = array(); - switch (true) { - case isset($key['e']): - $components['publicExponent'] = $key['e']->copy(); - break; - case isset($key['exponent']): - $components['publicExponent'] = $key['exponent']->copy(); - break; - case isset($key['publicExponent']): - $components['publicExponent'] = $key['publicExponent']->copy(); - break; - case isset($key[0]): - $components['publicExponent'] = $key[0]->copy(); - } - switch (true) { - case isset($key['n']): - $components['modulus'] = $key['n']->copy(); - break; - case isset($key['modulo']): - $components['modulus'] = $key['modulo']->copy(); - break; - case isset($key['modulus']): - $components['modulus'] = $key['modulus']->copy(); - break; - case isset($key[1]): - $components['modulus'] = $key[1]->copy(); - } - return $components; - case CRYPT_RSA_PRIVATE_FORMAT_PKCS1: - case CRYPT_RSA_PUBLIC_FORMAT_PKCS1: - /* Although PKCS#1 proposes a format that public and private keys can use, encrypting them is - "outside the scope" of PKCS#1. PKCS#1 then refers you to PKCS#12 and PKCS#15 if you're wanting to - protect private keys, however, that's not what OpenSSL* does. OpenSSL protects private keys by adding - two new "fields" to the key - DEK-Info and Proc-Type. These fields are discussed here: - - http://tools.ietf.org/html/rfc1421#section-4.6.1.1 - http://tools.ietf.org/html/rfc1421#section-4.6.1.3 - - DES-EDE3-CBC as an algorithm, however, is not discussed anywhere, near as I can tell. - DES-CBC and DES-EDE are discussed in RFC1423, however, DES-EDE3-CBC isn't, nor is its key derivation - function. As is, the definitive authority on this encoding scheme isn't the IETF but rather OpenSSL's - own implementation. ie. the implementation *is* the standard and any bugs that may exist in that - implementation are part of the standard, as well. - - * OpenSSL is the de facto standard. It's utilized by OpenSSH and other projects */ - if (preg_match('#DEK-Info: DES-EDE3-CBC,(.+)#', $key, $matches)) { - $iv = pack('H*', trim($matches[1])); - $symkey = pack('H*', md5($this->password . $iv)); // symkey is short for symmetric key - $symkey.= substr(pack('H*', md5($symkey . $this->password . $iv)), 0, 8); - $ciphertext = base64_decode(preg_replace('#.+(\r|\n|\r\n)\1|[\r\n]|-.+-#s', '', $key)); - if ($ciphertext === false) { - return false; - } - if (!class_exists('Crypt_TripleDES')) { - require_once('Crypt/TripleDES.php'); - } - $des = new Crypt_TripleDES(); - $des->setKey($symkey); - $des->setIV($iv); - $key = $des->decrypt($ciphertext); - } else { - $key = base64_decode(preg_replace('#-.+-|[\r\n]#', '', $key)); - if ($key === false) { - return false; - } - } - - $private = false; - $components = array(); - - $this->_string_shift($key); // skip over CRYPT_RSA_ASN1_SEQUENCE - $this->_decodeLength($key); // skip over the length of the above sequence - $this->_string_shift($key); // skip over CRYPT_RSA_ASN1_INTEGER - $length = $this->_decodeLength($key); - $temp = $this->_string_shift($key, $length); - if (strlen($temp) != 1 || ord($temp) > 2) { - $components['modulus'] = new Math_BigInteger($temp, -256); - $this->_string_shift($key); // skip over CRYPT_RSA_ASN1_INTEGER - $length = $this->_decodeLength($key); - $components[$type == CRYPT_RSA_PUBLIC_FORMAT_PKCS1 ? 'publicExponent' : 'privateExponent'] = new Math_BigInteger($this->_string_shift($key, $length), -256); - - return $components; - } - $this->_string_shift($key); // skip over CRYPT_RSA_ASN1_INTEGER - $length = $this->_decodeLength($key); - $components['modulus'] = new Math_BigInteger($this->_string_shift($key, $length), -256); - $this->_string_shift($key); - $length = $this->_decodeLength($key); - $components['publicExponent'] = new Math_BigInteger($this->_string_shift($key, $length), -256); - $this->_string_shift($key); - $length = $this->_decodeLength($key); - $components['privateExponent'] = new Math_BigInteger($this->_string_shift($key, $length), -256); - $this->_string_shift($key); - $length = $this->_decodeLength($key); - $components['primes'] = array(1 => new Math_BigInteger($this->_string_shift($key, $length), -256)); - $this->_string_shift($key); - $length = $this->_decodeLength($key); - $components['primes'][] = new Math_BigInteger($this->_string_shift($key, $length), -256); - $this->_string_shift($key); - $length = $this->_decodeLength($key); - $components['exponents'] = array(1 => new Math_BigInteger($this->_string_shift($key, $length), -256)); - $this->_string_shift($key); - $length = $this->_decodeLength($key); - $components['exponents'][] = new Math_BigInteger($this->_string_shift($key, $length), -256); - $this->_string_shift($key); - $length = $this->_decodeLength($key); - $components['coefficients'] = array(2 => new Math_BigInteger($this->_string_shift($key, $length), -256)); - if (!empty($key)) { - $key = substr($key, 1); // skip over CRYPT_RSA_ASN1_SEQUENCE - $this->_decodeLength($key); - while (!empty($key)) { - $key = substr($key, 1); // skip over CRYPT_RSA_ASN1_SEQUENCE - $this->_decodeLength($key); - $key = substr($key, 1); - $length = $this->_decodeLength($key); - $components['primes'][] = new Math_BigInteger($this->_string_shift($key, $length), -256); - $this->_string_shift($key); - $length = $this->_decodeLength($key); - $components['exponents'][] = new Math_BigInteger($this->_string_shift($key, $length), -256); - $this->_string_shift($key); - $length = $this->_decodeLength($key); - $components['coefficients'][] = new Math_BigInteger($this->_string_shift($key, $length), -256); - } - } - - return $components; - case CRYPT_RSA_PUBLIC_FORMAT_OPENSSH: - $key = base64_decode(preg_replace('#^ssh-rsa | .+$#', '', $key)); - if ($key === false) { - return false; - } - - $components = array(); - extract(unpack('Nlength', $this->_string_shift($key, 4))); - $components['modulus'] = new Math_BigInteger($this->_string_shift($key, $length), -256); - extract(unpack('Nlength', $this->_string_shift($key, 4))); - $components['publicExponent'] = new Math_BigInteger($this->_string_shift($key, $length), -256); - - return $components; - } - } - - /** - * Loads a public or private key - * - * @access public - * @param String $key - * @param Integer $type optional - */ - function loadKey($key, $type = CRYPT_RSA_PRIVATE_FORMAT_PKCS1) - { - $components = $this->_parseKey($key, $type); - $this->modulus = $components['modulus']; - $this->k = strlen($this->modulus->toBytes()); - $this->exponent = isset($components['privateExponent']) ? $components['privateExponent'] : $components['publicExponent']; - if (isset($components['primes'])) { - $this->primes = $components['primes']; - $this->exponents = $components['exponents']; - $this->coefficients = $components['coefficients']; - $this->publicExponent = $components['publicExponent']; - } else { - $this->primes = array(); - $this->exponents = array(); - $this->coefficients = array(); - $this->publicExponent = false; - } - } - - /** - * Sets the password - * - * Private keys can be encrypted with a password. To unset the password, pass in the empty string or false. - * Or rather, pass in $password such that empty($password) is true. - * - * @see createKey() - * @see loadKey() - * @access public - * @param String $password - */ - function setPassword($password) - { - $this->password = $password; - } - - /** - * Defines the public key - * - * Some private key formats define the public exponent and some don't. Those that don't define it are problematic when - * used in certain contexts. For example, in SSH-2, RSA authentication works by sending the public key along with a - * message signed by the private key to the server. The SSH-2 server looks the public key up in an index of public keys - * and if it's present then proceeds to verify the signature. Problem is, if your private key doesn't include the public - * exponent this won't work unless you manually add the public exponent. - * - * Do note that when a new key is loaded the index will be cleared. - * - * Returns true on success, false on failure - * - * @see getPublicKey() - * @access public - * @param String $key - * @param Integer $type optional - * @return Boolean - */ - function setPublicKey($key, $type = CRYPT_RSA_PUBLIC_FORMAT_PKCS1) - { - $components = $this->_parseKey($key, $type); - if (!$this->modulus->equals($components['modulus'])) { - return false; - } - $this->publicExponent = $components['publicExponent']; - } - - /** - * Returns the public key - * - * The public key is only returned under two circumstances - if the private key had the public key embedded within it - * or if the public key was set via setPublicKey(). If the currently loaded key is supposed to be the public key this - * function won't return it since this library, for the most part, doesn't distinguish between public and private keys. - * - * @see getPublicKey() - * @access public - * @param String $key - * @param Integer $type optional - */ - function getPublicKey($type = CRYPT_RSA_PUBLIC_FORMAT_PKCS1) - { - $oldFormat = $this->publicKeyFormat; - $this->publicKeyFormat = $type; - $temp = $this->_convertPublicKey($this->modulus, $this->publicExponent); - $this->publicKeyFormat = $oldFormat; - return $temp; - } - - /** - * Generates the smallest and largest numbers requiring $bits bits - * - * @access private - * @param Integer $bits - * @return Array - */ - function _generateMinMax($bits) - { - $bytes = $bits >> 3; - $min = str_repeat(chr(0), $bytes); - $max = str_repeat(chr(0xFF), $bytes); - $msb = $num_bits & 7; - if ($msb) { - $min = chr(1 << ($msb - 1)) . $min; - $max = chr((1 << $msb) - 1) . $max; - } else { - $min[0] = chr(0x80); - } - - return array( - 'min' => new Math_BigInteger($min, 256), - 'max' => new Math_BigInteger($max, 256) - ); - } - - /** - * DER-decode the length - * - * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See - * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 § 8.1.3} for more information. - * - * @access private - * @param String $string - * @return Integer - */ - function _decodeLength(&$string) - { - $length = ord($this->_string_shift($string)); - if ( $length & 0x80 ) { // definite length, long form - $length&= 0x7F; - $temp = $this->_string_shift($string, $length); - $start+= $length; - list(, $length) = unpack('N', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4)); - } - return $length; - } - - /** - * DER-encode the length - * - * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See - * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 § 8.1.3} for more information. - * - * @access private - * @param Integer $length - * @return String - */ - function _encodeLength($length) - { - if ($length <= 0x7F) { - return chr($length); - } - - $temp = ltrim(pack('N', $length), chr(0)); - return pack('Ca*', 0x80 | strlen($temp), $temp); - } - - /** - * String Shift - * - * Inspired by array_shift - * - * @param String $string - * @param optional Integer $index - * @return String - * @access private - */ - function _string_shift(&$string, $index = 1) - { - $substr = substr($string, 0, $index); - $string = substr($string, $index); - return $substr; - } - - /** - * Determines the private key format - * - * @see createKey() - * @access public - * @param Integer $format - */ - function setPrivateKeyFormat($format) - { - $this->privateKeyFormat = $format; - } - - /** - * Determines the public key format - * - * @see createKey() - * @access public - * @param Integer $format - */ - function setPublicKeyFormat($format) - { - $this->publicKeyFormat = $format; - } - - /** - * Determines which hashing function should be used - * - * Used with signature production / verification and (if the encryption mode is CRYPT_RSA_ENCRYPTION_OAEP) encryption and - * decryption. If $hash isn't supported, sha1 is used. - * - * @access public - * @param String $hash - */ - function setHash($hash) - { - // Crypt_Hash supports algorithms that PKCS#1 doesn't support. md5-96 and sha1-96, for example. - switch ($hash) { - case 'md2': - case 'md5': - case 'sha1': - case 'sha256': - case 'sha384': - case 'sha512': - $this->hash = new Crypt_Hash($hash); - $this->hLen = $this->hash->getLength(); - $this->hashName = $hash; - break; - default: - $this->hash = new Crypt_Hash('sha1'); - $this->hLen = $this->hash->getLength(); - $this->hashName = 'sha1'; - } - } - - /** - * Determines which hashing function should be used for the mask generation function - * - * The mask generation function is used by CRYPT_RSA_ENCRYPTION_OAEP and CRYPT_RSA_SIGNATURE_PSS and although it's - * best if Hash and MGFHash are set to the same thing this is not a requirement. - * - * @access public - * @param String $hash - */ - function setMGFHash($hash) - { - // Crypt_Hash supports algorithms that PKCS#1 doesn't support. md5-96 and sha1-96, for example. - switch ($hash) { - case 'md2': - case 'md5': - case 'sha1': - case 'sha256': - case 'sha384': - case 'sha512': - $this->mgfHash = new Crypt_Hash($hash); - break; - default: - $this->mgfHash = new Crypt_Hash('sha1'); - } - } - - /** - * Determines the salt length - * - * To quote from {@link http://tools.ietf.org/html/rfc3447#page-38 RFC3447#page-38}: - * - * Typical salt lengths in octets are hLen (the length of the output - * of the hash function Hash) and 0. - * - * @access public - * @param Integer $format - */ - function setSaltLength($sLen) - { - $this->sLen = $sLen; - } - - /** - * Generates a random string x bytes long - * - * @access public - * @param Integer $bytes - * @param optional Integer $nonzero - * @return String - */ - function _random($bytes, $nonzero = false) - { - $temp = ''; - if ($nonzero) { - for ($i = 0; $i < $bytes; $i++) { - $temp.= chr(crypt_random(1, 255)); - } - } else { - $ints = ($bytes + 1) >> 2; - for ($i = 0; $i < $ints; $i++) { - $temp.= pack('N', crypt_random()); - } - $temp = substr($temp, 0, $bytes); - } - return $temp; - } - - /** - * Integer-to-Octet-String primitive - * - * See {@link http://tools.ietf.org/html/rfc3447#section-4.1 RFC3447#section-4.1}. - * - * @access private - * @param Math_BigInteger $x - * @param Integer $xLen - * @return String - */ - function _i2osp($x, $xLen) - { - $x = $x->toBytes(); - if (strlen($x) > $xLen) { - user_error('Integer too large', E_USER_NOTICE); - return false; - } - return str_pad($x, $xLen, chr(0), STR_PAD_LEFT); - } - - /** - * Octet-String-to-Integer primitive - * - * See {@link http://tools.ietf.org/html/rfc3447#section-4.2 RFC3447#section-4.2}. - * - * @access private - * @param String $x - * @return Math_BigInteger - */ - function _os2ip($x) - { - return new Math_BigInteger($x, 256); - } - - /** - * Exponentiate with or without Chinese Remainder Theorem - * - * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.1 RFC3447#section-5.1.2}. - * - * @access private - * @param Math_BigInteger $x - * @return Math_BigInteger - */ - function _exponentiate($x) - { - if (empty($this->primes) || empty($this->coefficients) || empty($this->exponents)) { - return $x->modPow($this->exponent, $this->modulus); - } - - $num_primes = count($this->primes); - $m_i = array( - 1 => $x->modPow($this->exponents[1], $this->primes[1]), - 2 => $x->modPow($this->exponents[2], $this->primes[2]) - ); - $h = $m_i[1]->subtract($m_i[2]); - $h = $h->multiply($this->coefficients[2]); - list(, $h) = $h->divide($this->primes[1]); - $m = $m_i[2]->add($h->multiply($this->primes[2])); - - $r = $this->primes[1]; - for ($i = 3; $i <= $num_primes; $i++) { - $m_i = $x->modPow($this->exponents[$i], $this->primes[$i]); - - $r = $r->multiply($this->primes[$i - 1]); - - $h = $m_i->subtract($m); - $h = $h->multiply($this->coefficients[$i]); - list(, $h) = $h->divide($this->primes[$i]); - - $m = $m->add($r->multiply($h)); - } - - return $m; - } - - /** - * RSAEP - * - * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.1 RFC3447#section-5.1.1}. - * - * @access private - * @param Math_BigInteger $m - * @return Math_BigInteger - */ - function _rsaep($m) - { - if ($m->compare($this->zero) < 0 || $m->compare($this->modulus) > 0) { - user_error('Message representative out of range', E_USER_NOTICE); - return false; - } - return $this->_exponentiate($m); - } - - /** - * RSADP - * - * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.2 RFC3447#section-5.1.2}. - * - * @access private - * @param Math_BigInteger $c - * @return Math_BigInteger - */ - function _rsadp($c) - { - if ($c->compare($this->zero) < 0 || $c->compare($this->modulus) > 0) { - user_error('Ciphertext representative out of range', E_USER_NOTICE); - return false; - } - return $this->_exponentiate($c); - } - - /** - * RSASP1 - * - * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.1 RFC3447#section-5.2.1}. - * - * @access private - * @param Math_BigInteger $m - * @return Math_BigInteger - */ - function _rsasp1($m) - { - if ($m->compare($this->zero) < 0 || $m->compare($this->modulus) > 0) { - user_error('Message representative out of range', E_USER_NOTICE); - return false; - } - return $this->_exponentiate($m); - } - - /** - * RSAVP1 - * - * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.2 RFC3447#section-5.2.2}. - * - * @access private - * @param Math_BigInteger $s - * @return Math_BigInteger - */ - function _rsavp1($s) - { - if ($s->compare($this->zero) < 0 || $s->compare($this->modulus) > 0) { - user_error('Signature representative out of range', E_USER_NOTICE); - return false; - } - return $this->_exponentiate($s); - } - - /** - * MGF1 - * - * See {@link http://tools.ietf.org/html/rfc3447#section-B.2.1 RFC3447#section-B.2.1}. - * - * @access private - * @param String $mgfSeed - * @param Integer $mgfLen - * @return String - */ - function _mgf1($mgfSeed, $maskLen) - { - // if $maskLen would yield strings larger than 4GB, PKCS#1 suggests a "Mask too long" error be output. - - $t = ''; - $count = ceil($maskLen / $this->hLen); - for ($i = 0; $i < $count; $i++) { - $c = pack('N', $i); - $t.= $this->mgfHash->hash($mgfSeed . $c); - } - - return substr($t, 0, $maskLen); - } - - /** - * RSAES-OAEP-ENCRYPT - * - * See {@link http://tools.ietf.org/html/rfc3447#section-7.1.1 RFC3447#section-7.1.1} and - * {http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding OAES}. - * - * @access private - * @param String $m - * @param String $l - * @return String - */ - function _rsaes_oaep_encrypt($m, $l = '') - { - $mLen = strlen($m); - - // Length checking - - // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error - // be output. - - if ($mLen > $this->k - 2 * $this->hLen - 2) { - user_error('Message too long', E_USER_NOTICE); - return false; - } - - // EME-OAEP encoding - - $lHash = $this->hash->hash($l); - $ps = str_repeat(chr(0), $this->k - $mLen - 2 * $this->hLen - 2); - $db = $lHash . $ps . chr(1) . $m; - $seed = $this->_random($this->hLen); - $dbMask = $this->_mgf1($seed, $this->k - $this->hLen - 1); - $maskedDB = $db ^ $dbMask; - $seedMask = $this->_mgf1($maskedDB, $this->hLen); - $maskedSeed = $seed ^ $seedMask; - $em = chr(0) . $maskedSeed . $maskedDB; - - // RSA encryption - - $m = $this->_os2ip($em); - $c = $this->_rsaep($m); - $c = $this->_i2osp($c, $this->k); - - // Output the ciphertext C - - return $c; - } - - /** - * RSAES-OAEP-DECRYPT - * - * See {@link http://tools.ietf.org/html/rfc3447#section-7.1.2 RFC3447#section-7.1.2}. The fact that the error - * messages aren't distinguishable from one another hinders debugging, but, to quote from RFC3447#section-7.1.2: - * - * Note. Care must be taken to ensure that an opponent cannot - * distinguish the different error conditions in Step 3.g, whether by - * error message or timing, or, more generally, learn partial - * information about the encoded message EM. Otherwise an opponent may - * be able to obtain useful information about the decryption of the - * ciphertext C, leading to a chosen-ciphertext attack such as the one - * observed by Manger [36]. - * - * As for $l... to quote from {@link http://tools.ietf.org/html/rfc3447#page-17 RFC3447#page-17}: - * - * Both the encryption and the decryption operations of RSAES-OAEP take - * the value of a label L as input. In this version of PKCS #1, L is - * the empty string; other uses of the label are outside the scope of - * this document. - * - * @access private - * @param String $c - * @param String $l - * @return String - */ - function _rsaes_oaep_decrypt($c, $l = '') - { - // Length checking - - // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error - // be output. - - if (strlen($c) != $this->k || $this->k < 2 * $this->hLen + 2) { - user_error('Decryption error', E_USER_NOTICE); - return false; - } - - // RSA decryption - - $c = $this->_os2ip($c); - $m = $this->_rsadp($c); - if ($m === false) { - user_error('Decryption error', E_USER_NOTICE); - return false; - } - $em = $this->_i2osp($m, $this->k); - - // EME-OAEP decoding - - $lHash = $this->hash->hash($l); - $y = ord($em[0]); - $maskedSeed = substr($em, 1, $this->hLen); - $maskedDB = substr($em, $this->hLen + 1); - $seedMask = $this->_mgf1($maskedDB, $this->hLen); - $seed = $maskedSeed ^ $seedMask; - $dbMask = $this->_mgf1($seed, $this->k - $this->hLen - 1); - $db = $maskedDB ^ $dbMask; - $lHash2 = substr($db, 0, $this->hLen); - $m = substr($db, $this->hLen); - if ($lHash != $lHash2) { - user_error('Decryption error', E_USER_NOTICE); - return false; - } - $m = ltrim($m, chr(0)); - if (ord($m[0]) != 1) { - user_error('Decryption error', E_USER_NOTICE); - return false; - } - - // Output the message M - - return substr($m, 1); - } - - /** - * RSAES-PKCS1-V1_5-ENCRYPT - * - * See {@link http://tools.ietf.org/html/rfc3447#section-7.2.1 RFC3447#section-7.2.1}. - * - * @access private - * @param String $m - * @return String - */ - function _rsaes_pkcs1_v1_5_encrypt($m) - { - $mLen = strlen($m); - - // Length checking - - if ($mLen > $this->k - 11) { - user_error('Message too long', E_USER_NOTICE); - return false; - } - - // EME-PKCS1-v1_5 encoding - - $ps = $this->_random($this->k - $mLen - 3, true); - $em = chr(0) . chr(2) . $ps . chr(0) . $m; - - // RSA encryption - $m = $this->_os2ip($em); - $c = $this->_rsaep($m); - $c = $this->_i2osp($c, $this->k); - - // Output the ciphertext C - - return $c; - } - - /** - * RSAES-PKCS1-V1_5-DECRYPT - * - * See {@link http://tools.ietf.org/html/rfc3447#section-7.2.2 RFC3447#section-7.2.2}. - * - * @access private - * @param String $c - * @return String - */ - function _rsaes_pkcs1_v1_5_decrypt($c) - { - // Length checking - - if (strlen($c) != $this->k) { // or if k < 11 - user_error('Decryption error', E_USER_NOTICE); - return false; - } - - // RSA decryption - - $c = $this->_os2ip($c); - $m = $this->_rsadp($c); - if ($m === false) { - user_error('Decryption error', E_USER_NOTICE); - return false; - } - $em = $this->_i2osp($m, $this->k); - - // EME-PKCS1-v1_5 decoding - - if (ord($em[0]) != 0 || ord($em[1]) != 2) { - user_error('Decryption error', E_USER_NOTICE); - return false; - } - - $ps = substr($em, 2, strpos($em, chr(0), 2) - 2); - $m = substr($em, strlen($ps) + 3); - - if (strlen($ps) < 8) { - user_error('Decryption error', E_USER_NOTICE); - return false; - } - - // Output M - - return $m; - } - - /** - * EMSA-PSS-ENCODE - * - * See {@link http://tools.ietf.org/html/rfc3447#section-9.1.1 RFC3447#section-9.1.1}. - * - * @access private - * @param String $m - * @param Integer $emBits - */ - function _emsa_pss_encode($m, $emBits) - { - // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error - // be output. - - $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8) - $sLen = $this->sLen == false ? $this->hLen : $this->sLen; - - $mHash = $this->hash->hash($m); - if ($emLen < $this->hLen + $sLen + 2) { - user_error('Encoding error', E_USER_NOTICE); - return false; - } - - $salt = $this->_random($sLen); - $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt; - $h = $this->hash->hash($m2); - $ps = str_repeat(chr(0), $emLen - $sLen - $this->hLen - 2); - $db = $ps . chr(1) . $salt; - $dbMask = $this->_mgf1($h, $emLen - $this->hLen - 1); - $maskedDB = $db ^ $dbMask; - $maskedDB[0] = ~chr(0xFF << ($emBits & 7)) & $maskedDB[0]; - $em = $maskedDB . $h . chr(0xBC); - - return $em; - } - - /** - * EMSA-PSS-VERIFY - * - * See {@link http://tools.ietf.org/html/rfc3447#section-9.1.2 RFC3447#section-9.1.2}. - * - * @access private - * @param String $m - * @param String $em - * @param Integer $emBits - * @return String - */ - function _emsa_pss_verify($m, $em, $emBits) - { - // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error - // be output. - - $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8); - $sLen = $this->sLen == false ? $this->hLen : $this->sLen; - - $mHash = $this->hash->hash($m); - if ($emLen < $this->hLen + $sLen + 2) { - return false; - } - - if ($em[strlen($em) - 1] != chr(0xBC)) { - return false; - } - - $maskedDB = substr($em, 0, $em - $this->hLen - 1); - $h = substr($em, $em - $this->hLen - 1, $this->hLen); - $temp = chr(0xFF << ($emBits & 7)); - if ((~$maskedDB[0] & $temp) != $temp) { - return false; - } - $dbMask = $this->_mgf1($h, $emLen - $this->hLen - 1); - $db = $maskedDB ^ $dbMask; - $db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0]; - $temp = $emLen - $this->hLen - $sLen - 2; - if (substr($db, 0, $temp) != str_repeat(chr(0), $temp) || ord($db[$temp]) != 1) { - return false; - } - $salt = substr($db, $temp + 1); // should be $sLen long - $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt; - $h2 = $this->hash->hash($m2); - return $h == $h2; - } - - /** - * RSASSA-PSS-SIGN - * - * See {@link http://tools.ietf.org/html/rfc3447#section-8.1.1 RFC3447#section-8.1.1}. - * - * @access private - * @param String $m - * @return String - */ - function _rsassa_pss_sign($m) - { - // EMSA-PSS encoding - - $em = $this->_emsa_pss_encode($m, 8 * $this->k - 1); - - // RSA signature - - $m = $this->_os2ip($em); - $s = $this->_rsasp1($m); - $s = $this->_i2osp($s, $this->k); - - // Output the signature S - - return $s; - } - - /** - * RSASSA-PSS-VERIFY - * - * See {@link http://tools.ietf.org/html/rfc3447#section-8.1.2 RFC3447#section-8.1.2}. - * - * @access private - * @param String $m - * @param String $s - * @return String - */ - function _rsassa_pss_verify($m, $s) - { - // Length checking - - if (strlen($s) != $this->k) { - user_error('Invalid signature', E_USER_NOTICE); - return false; - } - - // RSA verification - - $modBits = 8 * $this->k; - - $s2 = $this->_os2ip($s); - $m2 = $this->_rsavp1($s2); - if ($m2 === false) { - user_error('Invalid signature', E_USER_NOTICE); - return false; - } - $em = $this->_i2osp($m2, $modBits >> 3); - if ($em === false) { - user_error('Invalid signature', E_USER_NOTICE); - return false; - } - - // EMSA-PSS verification - - return $this->_emsa_pss_verify($m, $em, $modBits - 1); - } - - /** - * EMSA-PKCS1-V1_5-ENCODE - * - * See {@link http://tools.ietf.org/html/rfc3447#section-9.2 RFC3447#section-9.2}. - * - * @access private - * @param String $m - * @param Integer $emLen - * @return String - */ - function _emsa_pkcs1_v1_5_encode($m, $emLen) - { - $h = $this->hash->hash($m); - if ($h === false) { - return false; - } - - // see http://tools.ietf.org/html/rfc3447#page-43 - switch ($this->hashName) { - case 'md2': - $t = pack('H*', '3020300c06082a864886f70d020205000410'); - break; - case 'md5': - $t = pack('H*', '3020300c06082a864886f70d020505000410'); - break; - case 'sha1': - $t = pack('H*', '3021300906052b0e03021a05000414'); - break; - case 'sha256': - $t = pack('H*', '3031300d060960864801650304020105000420'); - break; - case 'sha384': - $t = pack('H*', '3041300d060960864801650304020205000430'); - break; - case 'sha512': - $t = pack('H*', '3051300d060960864801650304020305000440'); - } - $t.= $h; - $tLen = strlen($t); - - if ($emLen < $tLen + 11) { - user_error('Intended encoded message length too short', E_USER_NOTICE); - return false; - } - - $ps = str_repeat(chr(0xFF), $emLen - $tLen - 3); - - $em = "\0\1$ps\0$t"; - - return $em; - } - - /** - * RSASSA-PKCS1-V1_5-SIGN - * - * See {@link http://tools.ietf.org/html/rfc3447#section-8.2.1 RFC3447#section-8.2.1}. - * - * @access private - * @param String $m - * @return String - */ - function _rsassa_pkcs1_v1_5_sign($m) - { - // EMSA-PKCS1-v1_5 encoding - - $em = $this->_emsa_pkcs1_v1_5_encode($m, $this->k); - if ($em === false) { - user_error('RSA modulus too short', E_USER_NOTICE); - return false; - } - - // RSA signature - - $m = $this->_os2ip($em); - $s = $this->_rsasp1($m); - $s = $this->_i2osp($s, $this->k); - - // Output the signature S - - return $s; - } - - /** - * RSASSA-PKCS1-V1_5-VERIFY - * - * See {@link http://tools.ietf.org/html/rfc3447#section-8.2.2 RFC3447#section-8.2.2}. - * - * @access private - * @param String $m - * @return String - */ - function _rsassa_pkcs1_v1_5_verify($m, $s) - { - // Length checking - - if (strlen($s) != $this->k) { - user_error('Invalid signature', E_USER_NOTICE); - return false; - } - - // RSA verification - - $s = $this->_os2ip($s); - $m2 = $this->_rsavp1($s); - if ($m2 === false) { - user_error('Invalid signature', E_USER_NOTICE); - return false; - } - $em = $this->_i2osp($m2, $this->k); - if ($em === false) { - user_error('Invalid signature', E_USER_NOTICE); - return false; - } - - // EMSA-PKCS1-v1_5 encoding - - $em2 = $this->_emsa_pkcs1_v1_5_encode($m, $this->k); - if ($em2 === false) { - user_error('RSA modulus too short', E_USER_NOTICE); - return false; - } - - // Compare - - return $em == $em2; - } - - /** - * Set Encryption Mode - * - * Valid values include CRYPT_RSA_ENCRYPTION_OAEP and CRYPT_RSA_ENCRYPTION_PKCS1. - * - * @access public - * @param Integer $mode - */ - function setEncryptionMode($mode) - { - $this->encryptionMode = $mode; - } - - /** - * Set Signature Mode - * - * Valid values include CRYPT_RSA_SIGNATURE_PSS and CRYPT_RSA_SIGNATURE_PKCS1 - * - * @access public - * @param Integer $mode - */ - function setSignatureMode($mode) - { - $this->signatureMode = $mode; - } - - /** - * Encryption - * - * Both CRYPT_RSA_ENCRYPTION_OAEP and CRYPT_RSA_ENCRYPTION_PKCS1 both place limits on how long $plaintext can be. - * If $plaintext exceeds those limits it will be broken up so that it does and the resultant ciphertext's will - * be concatenated together. - * - * @see decrypt() - * @access public - * @param String $plaintext - * @return String - */ - function encrypt($plaintext) - { - switch ($this->encryptionMode) { - case CRYPT_RSA_ENCRYPTION_PKCS1: - $plaintext = str_split($plaintext, $this->k - 11); - $ciphertext = ''; - foreach ($plaintext as $m) { - $ciphertext.= $this->_rsaes_pkcs1_v1_5_encrypt($m); - } - return $ciphertext; - //case CRYPT_RSA_ENCRYPTION_OAEP: - default: - $plaintext = str_split($plaintext, $this->k - 2 * $this->hLen - 2); - $ciphertext = ''; - foreach ($plaintext as $m) { - $ciphertext.= $this->_rsaes_oaep_encrypt($m); - } - return $ciphertext; - } - } - - /** - * Decryption - * - * @see encrypt() - * @access public - * @param String $plaintext - * @return String - */ - function decrypt($ciphertext) - { - switch ($this->encryptionMode) { - case CRYPT_RSA_ENCRYPTION_PKCS1: - $ciphertext = str_split($ciphertext, $this->k); - $plaintext = ''; - foreach ($ciphertext as $c) { - $temp = $this->_rsaes_pkcs1_v1_5_decrypt($c); - if ($temp === false) { - return false; - } - $plaintext.= $temp; - } - return $plaintext; - //case CRYPT_RSA_ENCRYPTION_OAEP: - default: - $ciphertext = str_split($ciphertext, $this->k); - $plaintext = ''; - foreach ($ciphertext as $c) { - $temp = $this->_rsaes_oaep_decrypt($c); - if ($temp === false) { - return false; - } - $plaintext.= $temp; - } - return $plaintext; - } - } - - /** - * Create a signature - * - * @see verify() - * @access public - * @param String $message - * @return String - */ - function sign($message) - { - switch ($this->signatureMode) { - case CRYPT_RSA_SIGNATURE_PKCS1: - return $this->_rsassa_pkcs1_v1_5_sign($message); - //case CRYPT_RSA_SIGNATURE_PSS: - default: - return $this->_rsassa_pss_sign($message); - } - } - - /** - * Verifies a signature - * - * @see sign() - * @access public - * @param String $message - * @param String $signature - * @return Boolean - */ - function verify($message, $signature) - { - switch ($this->signatureMode) { - case CRYPT_RSA_SIGNATURE_PKCS1: - return $this->_rsassa_pkcs1_v1_5_verify($message, $signature); - //case CRYPT_RSA_SIGNATURE_PSS: - default: - return $this->_rsassa_pss_verify($message, $signature); - } - } + + * createKey()); + * + * $plaintext = 'terrafrost'; + * + * $rsa->loadKey($privatekey); + * $ciphertext = $rsa->encrypt($plaintext); + * + * $rsa->loadKey($publickey); + * echo $rsa->decrypt($ciphertext); + * ?> + * + * + * Here's an example of how to create signatures and verify signatures with this library: + * + * createKey()); + * + * $plaintext = 'terrafrost'; + * + * $rsa->loadKey($privatekey); + * $signature = $rsa->sign($plaintext); + * + * $rsa->loadKey($publickey); + * echo $rsa->verify($plaintext, $signature) ? 'verified' : 'unverified'; + * ?> + * + * + * LICENSE: This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * @category Crypt + * @package Crypt_RSA + * @author Jim Wigginton + * @copyright MMIX Jim Wigginton + * @license http://www.gnu.org/licenses/lgpl.txt + * @version $Id: RSA.php,v 1.14 2010/03/01 17:28:19 terrafrost Exp $ + * @link http://phpseclib.sourceforge.net + */ + +/** + * Include Math_BigInteger + */ +require_once('Math/BigInteger.php'); + +/** + * Include Crypt_Random + */ +require_once('Crypt/Random.php'); + +/** + * Include Crypt_Hash + */ +require_once('Crypt/Hash.php'); + +/**#@+ + * @access public + * @see Crypt_RSA::encrypt() + * @see Crypt_RSA::decrypt() + */ +/** + * Use {@link http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding Optimal Asymmetric Encryption Padding} + * (OAEP) for encryption / decryption. + * + * Uses sha1 by default. + * + * @see Crypt_RSA::setHash() + * @see Crypt_RSA::setMGFHash() + */ +define('CRYPT_RSA_ENCRYPTION_OAEP', 1); +/** + * Use PKCS#1 padding. + * + * Although CRYPT_RSA_ENCRYPTION_OAEP offers more security, including PKCS#1 padding is necessary for purposes of backwards + * compatability with protocols (like SSH-1) written before OAEP's introduction. + */ +define('CRYPT_RSA_ENCRYPTION_PKCS1', 2); +/**#@-*/ + +/**#@+ + * @access public + * @see Crypt_RSA::sign() + * @see Crypt_RSA::verify() + * @see Crypt_RSA::setHash() + */ +/** + * Use the Probabilistic Signature Scheme for signing + * + * Uses sha1 by default. + * + * @see Crypt_RSA::setSaltLength() + * @see Crypt_RSA::setMGFHash() + */ +define('CRYPT_RSA_SIGNATURE_PSS', 1); +/** + * Use the PKCS#1 scheme by default. + * + * Although CRYPT_RSA_SIGNATURE_PSS offers more security, including PKCS#1 signing is necessary for purposes of backwards + * compatability with protocols (like SSH-2) written before PSS's introduction. + */ +define('CRYPT_RSA_SIGNATURE_PKCS1', 2); +/**#@-*/ + +/**#@+ + * @access private + * @see Crypt_RSA::createKey() + */ +/** + * ASN1 Integer + */ +define('CRYPT_RSA_ASN1_INTEGER', 2); +/** + * ASN1 Sequence (with the constucted bit set) + */ +define('CRYPT_RSA_ASN1_SEQUENCE', 48); +/**#@-*/ + +/**#@+ + * @access private + * @see Crypt_RSA::Crypt_RSA() + */ +/** + * To use the pure-PHP implementation + */ +define('CRYPT_RSA_MODE_INTERNAL', 1); +/** + * To use the OpenSSL library + * + * (if enabled; otherwise, the internal implementation will be used) + */ +define('CRYPT_RSA_MODE_OPENSSL', 2); +/**#@-*/ + +/**#@+ + * @access public + * @see Crypt_RSA::createKey() + * @see Crypt_RSA::setPrivateKeyFormat() + */ +/** + * PKCS#1 formatted private key + * + * Used by OpenSSH + */ +define('CRYPT_RSA_PRIVATE_FORMAT_PKCS1', 0); +/**#@-*/ + +/**#@+ + * @access public + * @see Crypt_RSA::createKey() + * @see Crypt_RSA::setPublicKeyFormat() + */ +/** + * Raw public key + * + * An array containing two Math_BigInteger objects. + * + * The exponent can be indexed with any of the following: + * + * 0, e, exponent, publicExponent + * + * The modulus can be indexed with any of the following: + * + * 1, n, modulo, modulus + */ +define('CRYPT_RSA_PUBLIC_FORMAT_RAW', 1); +/** + * PKCS#1 formatted public key + */ +define('CRYPT_RSA_PUBLIC_FORMAT_PKCS1', 2); +/** + * OpenSSH formatted public key + * + * Place in $HOME/.ssh/authorized_keys + */ +define('CRYPT_RSA_PUBLIC_FORMAT_OPENSSH', 3); +/**#@-*/ + +/** + * Pure-PHP PKCS#1 compliant implementation of RSA. + * + * @author Jim Wigginton + * @version 0.1.0 + * @access public + * @package Crypt_RSA + */ +class Crypt_RSA { + /** + * Precomputed Zero + * + * @var Array + * @access private + */ + var $zero; + + /** + * Precomputed One + * + * @var Array + * @access private + */ + var $one; + + /** + * Private Key Format + * + * @var Integer + * @access private + */ + var $privateKeyFormat = CRYPT_RSA_PRIVATE_FORMAT_PKCS1; + + /** + * Public Key Format + * + * @var Integer + * @access public + */ + var $publicKeyFormat = CRYPT_RSA_PUBLIC_FORMAT_PKCS1; + + /** + * Modulus (ie. n) + * + * @var Math_BigInteger + * @access private + */ + var $modulus; + + /** + * Modulus length + * + * @var Math_BigInteger + * @access private + */ + var $k; + + /** + * Exponent (ie. e or d) + * + * @var Math_BigInteger + * @access private + */ + var $exponent; + + /** + * Primes for Chinese Remainder Theorem (ie. p and q) + * + * @var Array + * @access private + */ + var $primes; + + /** + * Exponents for Chinese Remainder Theorem (ie. dP and dQ) + * + * @var Array + * @access private + */ + var $exponents; + + /** + * Coefficients for Chinese Remainder Theorem (ie. qInv) + * + * @var Array + * @access private + */ + var $coefficients; + + /** + * Hash name + * + * @var String + * @access private + */ + var $hashName; + + /** + * Hash function + * + * @var Crypt_Hash + * @access private + */ + var $hash; + + /** + * Length of hash function output + * + * @var Integer + * @access private + */ + var $hLen; + + /** + * Length of salt + * + * @var Integer + * @access private + */ + var $sLen; + + /** + * Hash function for the Mask Generation Function + * + * @var Crypt_Hash + * @access private + */ + var $mgfHash; + + /** + * Length of MGF hash function output + * + * @var Integer + * @access private + */ + var $mgfHLen; + + /** + * Encryption mode + * + * @var Integer + * @access private + */ + var $encryptionMode = CRYPT_RSA_ENCRYPTION_OAEP; + + /** + * Signature mode + * + * @var Integer + * @access private + */ + var $signatureMode = CRYPT_RSA_SIGNATURE_PSS; + + /** + * Public Exponent + * + * @var Mixed + * @access private + */ + var $publicExponent = false; + + /** + * Password + * + * @var String + * @access private + */ + var $password = ''; + + /** + * The constructor + * + * If you want to make use of the openssl extension, you'll need to set the mode manually, yourself. The reason + * Crypt_RSA doesn't do it is because OpenSSL doesn't fail gracefully. openssl_pkey_new(), in particular, requires + * openssl.cnf be present somewhere and, unfortunately, the only real way to find out is too late. + * + * @return Crypt_RSA + * @access public + */ + function Crypt_RSA() + { + if ( !defined('CRYPT_RSA_MODE') ) { + switch (true) { + //case extension_loaded('openssl') && version_compare(PHP_VERSION, '4.2.0', '>='): + // define('CRYPT_RSA_MODE', CRYPT_RSA_MODE_OPENSSL); + // break; + default: + define('CRYPT_RSA_MODE', CRYPT_RSA_MODE_INTERNAL); + } + } + + $this->zero = new Math_BigInteger(); + $this->one = new Math_BigInteger(1); + + $this->hash = new Crypt_Hash('sha1'); + $this->hLen = $this->hash->getLength(); + $this->hashName = 'sha1'; + $this->mgfHash = new Crypt_Hash('sha1'); + $this->mgfHLen = $this->mgfHash->getLength(); + } + + /** + * Create public / private key pair + * + * Returns an array with the following three elements: + * - 'privatekey': The private key. + * - 'publickey': The public key. + * - 'partialkey': A partially computed key (if the execution time exceeded $timeout). + * Will need to be passed back to Crypt_RSA::createKey() as the third parameter for further processing. + * + * @access public + * @param optional Integer $bits + * @param optional Integer $timeout + * @param optional Math_BigInteger $p + */ + function createKey($bits = 1024, $timeout = false, $partial = array()) + { + if ( CRYPT_RSA_MODE == CRYPT_RSA_MODE_OPENSSL ) { + $rsa = openssl_pkey_new(array('private_key_bits' => $bits)); + openssl_pkey_export($rsa, $privatekey); + $publickey = openssl_pkey_get_details($rsa); + $publickey = $publickey['key']; + + if ($this->privateKeyFormat != CRYPT_RSA_PRIVATE_FORMAT_PKCS1) { + $privatekey = call_user_func_array(array($this, '_convertPrivateKey'), array_values($this->_parseKey($privatekey, CRYPT_RSA_PRIVATE_FORMAT_PKCS1))); + $publickey = call_user_func_array(array($this, '_convertPublicKey'), array_values($this->_parseKey($publickey, CRYPT_RSA_PUBLIC_FORMAT_PKCS1))); + } + + return array( + 'privatekey' => $privatekey, + 'publickey' => $publickey, + 'partialkey' => false + ); + } + + static $e; + if (!isset($e)) { + if (!defined('CRYPT_RSA_EXPONENT')) { + // http://en.wikipedia.org/wiki/65537_%28number%29 + define('CRYPT_RSA_EXPONENT', '65537'); + } + if (!defined('CRYPT_RSA_COMMENT')) { + define('CRYPT_RSA_COMMENT', 'phpseclib-generated-key'); + } + // per , this number ought not result in primes smaller + // than 256 bits. + if (!defined('CRYPT_RSA_SMALLEST_PRIME')) { + define('CRYPT_RSA_SMALLEST_PRIME', 4096); + } + + $e = new Math_BigInteger(CRYPT_RSA_EXPONENT); + } + + extract($this->_generateMinMax($bits)); + $absoluteMin = $min; + $temp = $bits >> 1; + if ($temp > CRYPT_RSA_SMALLEST_PRIME) { + $num_primes = floor($bits / CRYPT_RSA_SMALLEST_PRIME); + $temp = CRYPT_RSA_SMALLEST_PRIME; + } else { + $num_primes = 2; + } + extract($this->_generateMinMax($temp + $bits % $temp)); + $finalMax = $max; + extract($this->_generateMinMax($temp)); + + $generator = new Math_BigInteger(); + $generator->setRandomGenerator('crypt_random'); + + $n = $this->one->copy(); + if (!empty($partial)) { + extract(unserialize($partial)); + } else { + $exponents = $coefficients = $primes = array(); + $lcm = array( + 'top' => $this->one->copy(), + 'bottom' => false + ); + } + + $start = time(); + $i0 = count($primes) + 1; + + do { + for ($i = $i0; $i <= $num_primes; $i++) { + if ($timeout !== false) { + $timeout-= time() - $start; + $start = time(); + if ($timeout <= 0) { + return serialize(array( + 'privatekey' => '', + 'publickey' => '', + 'partialkey' => array( + 'primes' => $primes, + 'coefficients' => $coefficients, + 'lcm' => $lcm, + 'exponents' => $exponents + ) + )); + } + } + + if ($i == $num_primes) { + list($min, $temp) = $absoluteMin->divide($n); + if (!$temp->equals($this->zero)) { + $min = $min->add($this->one); // ie. ceil() + } + $primes[$i] = $generator->randomPrime($min, $finalMax, $timeout); + } else { + $primes[$i] = $generator->randomPrime($min, $max, $timeout); + } + + if ($primes[$i] === false) { // if we've reached the timeout + return array( + 'privatekey' => '', + 'publickey' => '', + 'partialkey' => empty($primes) ? '' : serialize(array( + 'primes' => array_slice($primes, 0, $i - 1), + 'coefficients' => $coefficients, + 'lcm' => $lcm, + 'exponents' => $exponents + )) + ); + } + + // the first coefficient is calculated differently from the rest + // ie. instead of being $primes[1]->modInverse($primes[2]), it's $primes[2]->modInverse($primes[1]) + if ($i > 2) { + $coefficients[$i] = $n->modInverse($primes[$i]); + } + + $n = $n->multiply($primes[$i]); + + $temp = $primes[$i]->subtract($this->one); + + // textbook RSA implementations use Euler's totient function instead of the least common multiple. + // see http://en.wikipedia.org/wiki/Euler%27s_totient_function + $lcm['top'] = $lcm['top']->multiply($temp); + $lcm['bottom'] = $lcm['bottom'] === false ? $temp : $lcm['bottom']->gcd($temp); + + $exponents[$i] = $e->modInverse($temp); + } + + list($lcm) = $lcm['top']->divide($lcm['bottom']); + $gcd = $lcm->gcd($e); + $i0 = 1; + } while (!$gcd->equals($this->one)); + + $d = $e->modInverse($lcm); + + $coefficients[2] = $primes[2]->modInverse($primes[1]); + + // from : + // RSAPrivateKey ::= SEQUENCE { + // version Version, + // modulus INTEGER, -- n + // publicExponent INTEGER, -- e + // privateExponent INTEGER, -- d + // prime1 INTEGER, -- p + // prime2 INTEGER, -- q + // exponent1 INTEGER, -- d mod (p-1) + // exponent2 INTEGER, -- d mod (q-1) + // coefficient INTEGER, -- (inverse of q) mod p + // otherPrimeInfos OtherPrimeInfos OPTIONAL + // } + + return array( + 'privatekey' => $this->_convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients), + 'publickey' => $this->_convertPublicKey($n, $e), + 'partialkey' => false + ); + } + + /** + * Convert a private key to the appropriate format. + * + * @access private + * @see setPrivateKeyFormat() + * @param String $RSAPrivateKey + * @return String + */ + function _convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients) + { + $num_primes = count($primes); + $raw = array( + 'version' => $num_primes == 2 ? chr(0) : chr(1), // two-prime vs. multi + 'modulus' => $n->toBytes(true), + 'publicExponent' => $e->toBytes(true), + 'privateExponent' => $d->toBytes(true), + 'prime1' => $primes[1]->toBytes(true), + 'prime2' => $primes[2]->toBytes(true), + 'exponent1' => $exponents[1]->toBytes(true), + 'exponent2' => $exponents[2]->toBytes(true), + 'coefficient' => $coefficients[2]->toBytes(true) + ); + + // if the format in question does not support multi-prime rsa and multi-prime rsa was used, + // call _convertPublicKey() instead. + switch ($this->privateKeyFormat) { + default: // eg. CRYPT_RSA_PRIVATE_FORMAT_PKCS1 + $components = array(); + foreach ($raw as $name => $value) { + $components[$name] = pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($value)), $value); + } + + $RSAPrivateKey = implode('', $components); + + if ($num_primes > 2) { + $OtherPrimeInfos = ''; + for ($i = 3; $i <= $num_primes; $i++) { + // OtherPrimeInfos ::= SEQUENCE SIZE(1..MAX) OF OtherPrimeInfo + // + // OtherPrimeInfo ::= SEQUENCE { + // prime INTEGER, -- ri + // exponent INTEGER, -- di + // coefficient INTEGER -- ti + // } + $OtherPrimeInfo = pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($primes[$i]->toBytes(true))), $primes[$i]->toBytes(true)); + $OtherPrimeInfo.= pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($exponents[$i]->toBytes(true))), $exponents[$i]->toBytes(true)); + $OtherPrimeInfo.= pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($coefficients[$i]->toBytes(true))), $coefficients[$i]->toBytes(true)); + $OtherPrimeInfos.= pack('Ca*a*', CRYPT_RSA_ASN1_SEQUENCE, $this->_encodeLength(strlen($OtherPrimeInfo)), $OtherPrimeInfo); + } + $RSAPrivateKey.= pack('Ca*a*', CRYPT_RSA_ASN1_SEQUENCE, $this->_encodeLength(strlen($OtherPrimeInfos)), $OtherPrimeInfos); + } + + $RSAPrivateKey = pack('Ca*a*', CRYPT_RSA_ASN1_SEQUENCE, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey); + + if (!empty($this->password)) { + $iv = $this->_random(8); + $symkey = pack('H*', md5($this->password . $iv)); // symkey is short for symmetric key + $symkey.= substr(pack('H*', md5($symkey . $this->password . $iv)), 0, 8); + if (!class_exists('Crypt_TripleDES')) { + require_once('Crypt/TripleDES.php'); + } + $des = new Crypt_TripleDES(); + $des->setKey($symkey); + $des->setIV($iv); + $iv = strtoupper(bin2hex($iv)); + $RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n" . + "Proc-Type: 4,ENCRYPTED\r\n" . + "DEK-Info: DES-EDE3-CBC,$iv\r\n" . + "\r\n" . + chunk_split(base64_encode($des->encrypt($RSAPrivateKey))) . + '-----END RSA PRIVATE KEY-----'; + } else { + $RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n" . + chunk_split(base64_encode($RSAPrivateKey)) . + '-----END RSA PRIVATE KEY-----'; + } + + return $RSAPrivateKey; + } + } + + /** + * Convert a public key to the appropriate format + * + * @access private + * @see setPublicKeyFormat() + * @param String $RSAPrivateKey + * @return String + */ + function _convertPublicKey($n, $e) + { + $modulus = $n->toBytes(true); + $publicExponent = $e->toBytes(true); + + switch ($this->publicKeyFormat) { + case CRYPT_RSA_PUBLIC_FORMAT_RAW: + return array('e' => $e->copy(), 'n' => $n->copy()); + case CRYPT_RSA_PUBLIC_FORMAT_OPENSSH: + // from : + // string "ssh-rsa" + // mpint e + // mpint n + $RSAPublicKey = pack('Na*Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($publicExponent), $publicExponent, strlen($modulus), $modulus); + $RSAPublicKey = 'ssh-rsa ' . base64_encode($RSAPublicKey) . ' ' . CRYPT_RSA_COMMENT; + + return $RSAPublicKey; + default: // eg. CRYPT_RSA_PUBLIC_FORMAT_PKCS1 + // from : + // RSAPublicKey ::= SEQUENCE { + // modulus INTEGER, -- n + // publicExponent INTEGER -- e + // } + $components = array( + 'modulus' => pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($modulus)), $modulus), + 'publicExponent' => pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($publicExponent)), $publicExponent) + ); + + $RSAPublicKey = pack('Ca*a*a*', + CRYPT_RSA_ASN1_SEQUENCE, $this->_encodeLength(strlen($components['modulus']) + strlen($components['publicExponent'])), + $components['modulus'], $components['publicExponent'] + ); + + $RSAPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" . + chunk_split(base64_encode($RSAPublicKey)) . + '-----END PUBLIC KEY-----'; + + return $RSAPublicKey; + } + } + + /** + * Break a public or private key down into its constituant components + * + * @access private + * @see _convertPublicKey() + * @see _convertPrivateKey() + * @param String $key + * @param Integer $type + * @return Array + */ + function _parseKey($key, $type) + { + switch ($type) { + case CRYPT_RSA_PUBLIC_FORMAT_RAW: + if (!is_array($key)) { + return false; + } + $components = array(); + switch (true) { + case isset($key['e']): + $components['publicExponent'] = $key['e']->copy(); + break; + case isset($key['exponent']): + $components['publicExponent'] = $key['exponent']->copy(); + break; + case isset($key['publicExponent']): + $components['publicExponent'] = $key['publicExponent']->copy(); + break; + case isset($key[0]): + $components['publicExponent'] = $key[0]->copy(); + } + switch (true) { + case isset($key['n']): + $components['modulus'] = $key['n']->copy(); + break; + case isset($key['modulo']): + $components['modulus'] = $key['modulo']->copy(); + break; + case isset($key['modulus']): + $components['modulus'] = $key['modulus']->copy(); + break; + case isset($key[1]): + $components['modulus'] = $key[1]->copy(); + } + return $components; + case CRYPT_RSA_PRIVATE_FORMAT_PKCS1: + case CRYPT_RSA_PUBLIC_FORMAT_PKCS1: + /* Although PKCS#1 proposes a format that public and private keys can use, encrypting them is + "outside the scope" of PKCS#1. PKCS#1 then refers you to PKCS#12 and PKCS#15 if you're wanting to + protect private keys, however, that's not what OpenSSL* does. OpenSSL protects private keys by adding + two new "fields" to the key - DEK-Info and Proc-Type. These fields are discussed here: + + http://tools.ietf.org/html/rfc1421#section-4.6.1.1 + http://tools.ietf.org/html/rfc1421#section-4.6.1.3 + + DES-EDE3-CBC as an algorithm, however, is not discussed anywhere, near as I can tell. + DES-CBC and DES-EDE are discussed in RFC1423, however, DES-EDE3-CBC isn't, nor is its key derivation + function. As is, the definitive authority on this encoding scheme isn't the IETF but rather OpenSSL's + own implementation. ie. the implementation *is* the standard and any bugs that may exist in that + implementation are part of the standard, as well. + + * OpenSSL is the de facto standard. It's utilized by OpenSSH and other projects */ + if (preg_match('#DEK-Info: (.+),(.+)#', $key, $matches)) { + $iv = pack('H*', trim($matches[2])); + $symkey = pack('H*', md5($this->password . $iv)); // symkey is short for symmetric key + $symkey.= substr(pack('H*', md5($symkey . $this->password . $iv)), 0, 8); + $ciphertext = preg_replace('#.+(\r|\n|\r\n)\1|[\r\n]|-.+-#s', '', $key); + $ciphertext = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $ciphertext) ? base64_decode($ciphertext) : false; + if ($ciphertext === false) { + $ciphertext = $key; + } + switch ($matches[1]) { + case 'DES-EDE3-CBC': + if (!class_exists('Crypt_TripleDES')) { + require_once('Crypt/TripleDES.php'); + } + $crypto = new Crypt_TripleDES(); + break; + case 'DES-CBC': + if (!class_exists('Crypt_DES')) { + require_once('Crypt/DES.php'); + } + $crypto = new Crypt_DES(); + break; + default: + return false; + } + $crypto->setKey($symkey); + $crypto->setIV($iv); + $decoded = $crypto->decrypt($ciphertext); + } else { + $decoded = preg_replace('#-.+-|[\r\n]#', '', $key); + $decoded = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $decoded) ? base64_decode($decoded) : false; + } + + if ($decoded !== false) { + $key = $decoded; + } + + $components = array(); + + if (ord($this->_string_shift($key)) != CRYPT_RSA_ASN1_SEQUENCE) { + return false; + } + if ($this->_decodeLength($key) != strlen($key)) { + return false; + } + + $tag = ord($this->_string_shift($key)); + if ($tag == CRYPT_RSA_ASN1_SEQUENCE) { + /* intended for keys for which OpenSSL's asn1parse returns the following: + + 0:d=0 hl=4 l= 290 cons: SEQUENCE + 4:d=1 hl=2 l= 13 cons: SEQUENCE + 6:d=2 hl=2 l= 9 prim: OBJECT :rsaEncryption + 17:d=2 hl=2 l= 0 prim: NULL + 19:d=1 hl=4 l= 271 prim: BIT STRING */ + $this->_string_shift($key, $this->_decodeLength($key)); + $this->_string_shift($key); // skip over the BIT STRING tag + $this->_decodeLength($key); // skip over the BIT STRING length + // "The initial octet shall encode, as an unsigned binary integer wtih bit 1 as the least significant bit, the number of + // unused bits in teh final subsequent octet. The number shall be in the range zero to seven." + // -- http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf (section 8.6.2.2) + $this->_string_shift($key); + if (ord($this->_string_shift($key)) != CRYPT_RSA_ASN1_SEQUENCE) { + return false; + } + if ($this->_decodeLength($key) != strlen($key)) { + return false; + } + $tag = ord($this->_string_shift($key)); + } + if ($tag != CRYPT_RSA_ASN1_INTEGER) { + return false; + } + + $length = $this->_decodeLength($key); + $temp = $this->_string_shift($key, $length); + if (strlen($temp) != 1 || ord($temp) > 2) { + $components['modulus'] = new Math_BigInteger($temp, -256); + $this->_string_shift($key); // skip over CRYPT_RSA_ASN1_INTEGER + $length = $this->_decodeLength($key); + $components[$type == CRYPT_RSA_PUBLIC_FORMAT_PKCS1 ? 'publicExponent' : 'privateExponent'] = new Math_BigInteger($this->_string_shift($key, $length), -256); + + return $components; + } + if (ord($this->_string_shift($key)) != CRYPT_RSA_ASN1_INTEGER) { + return false; + } + $length = $this->_decodeLength($key); + $components['modulus'] = new Math_BigInteger($this->_string_shift($key, $length), -256); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['publicExponent'] = new Math_BigInteger($this->_string_shift($key, $length), -256); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['privateExponent'] = new Math_BigInteger($this->_string_shift($key, $length), -256); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['primes'] = array(1 => new Math_BigInteger($this->_string_shift($key, $length), -256)); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['primes'][] = new Math_BigInteger($this->_string_shift($key, $length), -256); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['exponents'] = array(1 => new Math_BigInteger($this->_string_shift($key, $length), -256)); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['exponents'][] = new Math_BigInteger($this->_string_shift($key, $length), -256); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['coefficients'] = array(2 => new Math_BigInteger($this->_string_shift($key, $length), -256)); + + if (!empty($key)) { + if (ord($this->_string_shift($key)) != CRYPT_RSA_ASN1_SEQUENCE) { + return false; + } + $this->_decodeLength($key); + while (!empty($key)) { + if (ord($this->_string_shift($key)) != CRYPT_RSA_ASN1_SEQUENCE) { + return false; + } + $this->_decodeLength($key); + $key = substr($key, 1); + $length = $this->_decodeLength($key); + $components['primes'][] = new Math_BigInteger($this->_string_shift($key, $length), -256); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['exponents'][] = new Math_BigInteger($this->_string_shift($key, $length), -256); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['coefficients'][] = new Math_BigInteger($this->_string_shift($key, $length), -256); + } + } + + return $components; + case CRYPT_RSA_PUBLIC_FORMAT_OPENSSH: + $key = base64_decode(preg_replace('#^ssh-rsa | .+$#', '', $key)); + if ($key === false) { + return false; + } + + $cleanup = substr($key, 0, 11) == "\0\0\0\7ssh-rsa"; + + extract(unpack('Nlength', $this->_string_shift($key, 4))); + $publicExponent = new Math_BigInteger($this->_string_shift($key, $length), -256); + extract(unpack('Nlength', $this->_string_shift($key, 4))); + $modulus = new Math_BigInteger($this->_string_shift($key, $length), -256); + + if ($cleanup && strlen($key)) { + extract(unpack('Nlength', $this->_string_shift($key, 4))); + return array( + 'modulus' => new Math_BigInteger($this->_string_shift($key, $length), -256), + 'publicExponent' => $modulus + ); + } else { + return array( + 'modulus' => $modulus, + 'publicExponent' => $publicExponent + ); + } + } + } + + /** + * Loads a public or private key + * + * Returns true on success and false on failure (ie. an incorrect password was provided or the key was malformed) + * + * @access public + * @param String $key + * @param Integer $type optional + */ + function loadKey($key, $type = CRYPT_RSA_PRIVATE_FORMAT_PKCS1) + { + $components = $this->_parseKey($key, $type); + if ($components === false) { + return false; + } + + $this->modulus = $components['modulus']; + $this->k = strlen($this->modulus->toBytes()); + $this->exponent = isset($components['privateExponent']) ? $components['privateExponent'] : $components['publicExponent']; + if (isset($components['primes'])) { + $this->primes = $components['primes']; + $this->exponents = $components['exponents']; + $this->coefficients = $components['coefficients']; + $this->publicExponent = $components['publicExponent']; + } else { + $this->primes = array(); + $this->exponents = array(); + $this->coefficients = array(); + $this->publicExponent = false; + } + + return true; + } + + /** + * Sets the password + * + * Private keys can be encrypted with a password. To unset the password, pass in the empty string or false. + * Or rather, pass in $password such that empty($password) is true. + * + * @see createKey() + * @see loadKey() + * @access public + * @param String $password + */ + function setPassword($password) + { + $this->password = $password; + } + + /** + * Defines the public key + * + * Some private key formats define the public exponent and some don't. Those that don't define it are problematic when + * used in certain contexts. For example, in SSH-2, RSA authentication works by sending the public key along with a + * message signed by the private key to the server. The SSH-2 server looks the public key up in an index of public keys + * and if it's present then proceeds to verify the signature. Problem is, if your private key doesn't include the public + * exponent this won't work unless you manually add the public exponent. + * + * Do note that when a new key is loaded the index will be cleared. + * + * Returns true on success, false on failure + * + * @see getPublicKey() + * @access public + * @param String $key + * @param Integer $type optional + * @return Boolean + */ + function setPublicKey($key, $type = CRYPT_RSA_PUBLIC_FORMAT_PKCS1) + { + $components = $this->_parseKey($key, $type); + if (empty($this->modulus) || !$this->modulus->equals($components['modulus'])) { + return false; + } + $this->publicExponent = $components['publicExponent']; + } + + /** + * Returns the public key + * + * The public key is only returned under two circumstances - if the private key had the public key embedded within it + * or if the public key was set via setPublicKey(). If the currently loaded key is supposed to be the public key this + * function won't return it since this library, for the most part, doesn't distinguish between public and private keys. + * + * @see getPublicKey() + * @access public + * @param String $key + * @param Integer $type optional + */ + function getPublicKey($type = CRYPT_RSA_PUBLIC_FORMAT_PKCS1) + { + if (empty($this->modulus) || empty($this->publicExponent)) { + return false; + } + + $oldFormat = $this->publicKeyFormat; + $this->publicKeyFormat = $type; + $temp = $this->_convertPublicKey($this->modulus, $this->publicExponent); + $this->publicKeyFormat = $oldFormat; + return $temp; + } + + /** + * Generates the smallest and largest numbers requiring $bits bits + * + * @access private + * @param Integer $bits + * @return Array + */ + function _generateMinMax($bits) + { + $bytes = $bits >> 3; + $min = str_repeat(chr(0), $bytes); + $max = str_repeat(chr(0xFF), $bytes); + $msb = $bits & 7; + if ($msb) { + $min = chr(1 << ($msb - 1)) . $min; + $max = chr((1 << $msb) - 1) . $max; + } else { + $min[0] = chr(0x80); + } + + return array( + 'min' => new Math_BigInteger($min, 256), + 'max' => new Math_BigInteger($max, 256) + ); + } + + /** + * DER-decode the length + * + * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See + * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 § 8.1.3} for more information. + * + * @access private + * @param String $string + * @return Integer + */ + function _decodeLength(&$string) + { + $length = ord($this->_string_shift($string)); + if ( $length & 0x80 ) { // definite length, long form + $length&= 0x7F; + $temp = $this->_string_shift($string, $length); + list(, $length) = unpack('N', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4)); + } + return $length; + } + + /** + * DER-encode the length + * + * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See + * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 § 8.1.3} for more information. + * + * @access private + * @param Integer $length + * @return String + */ + function _encodeLength($length) + { + if ($length <= 0x7F) { + return chr($length); + } + + $temp = ltrim(pack('N', $length), chr(0)); + return pack('Ca*', 0x80 | strlen($temp), $temp); + } + + /** + * String Shift + * + * Inspired by array_shift + * + * @param String $string + * @param optional Integer $index + * @return String + * @access private + */ + function _string_shift(&$string, $index = 1) + { + $substr = substr($string, 0, $index); + $string = substr($string, $index); + return $substr; + } + + /** + * Determines the private key format + * + * @see createKey() + * @access public + * @param Integer $format + */ + function setPrivateKeyFormat($format) + { + $this->privateKeyFormat = $format; + } + + /** + * Determines the public key format + * + * @see createKey() + * @access public + * @param Integer $format + */ + function setPublicKeyFormat($format) + { + $this->publicKeyFormat = $format; + } + + /** + * Determines which hashing function should be used + * + * Used with signature production / verification and (if the encryption mode is CRYPT_RSA_ENCRYPTION_OAEP) encryption and + * decryption. If $hash isn't supported, sha1 is used. + * + * @access public + * @param String $hash + */ + function setHash($hash) + { + // Crypt_Hash supports algorithms that PKCS#1 doesn't support. md5-96 and sha1-96, for example. + switch ($hash) { + case 'md2': + case 'md5': + case 'sha1': + case 'sha256': + case 'sha384': + case 'sha512': + $this->hash = new Crypt_Hash($hash); + $this->hashName = $hash; + break; + default: + $this->hash = new Crypt_Hash('sha1'); + $this->hashName = 'sha1'; + } + $this->hLen = $this->hash->getLength(); + } + + /** + * Determines which hashing function should be used for the mask generation function + * + * The mask generation function is used by CRYPT_RSA_ENCRYPTION_OAEP and CRYPT_RSA_SIGNATURE_PSS and although it's + * best if Hash and MGFHash are set to the same thing this is not a requirement. + * + * @access public + * @param String $hash + */ + function setMGFHash($hash) + { + // Crypt_Hash supports algorithms that PKCS#1 doesn't support. md5-96 and sha1-96, for example. + switch ($hash) { + case 'md2': + case 'md5': + case 'sha1': + case 'sha256': + case 'sha384': + case 'sha512': + $this->mgfHash = new Crypt_Hash($hash); + break; + default: + $this->mgfHash = new Crypt_Hash('sha1'); + } + $this->mgfHLen = $this->mgfHash->getLength(); + } + + /** + * Determines the salt length + * + * To quote from {@link http://tools.ietf.org/html/rfc3447#page-38 RFC3447#page-38}: + * + * Typical salt lengths in octets are hLen (the length of the output + * of the hash function Hash) and 0. + * + * @access public + * @param Integer $format + */ + function setSaltLength($sLen) + { + $this->sLen = $sLen; + } + + /** + * Generates a random string x bytes long + * + * @access public + * @param Integer $bytes + * @param optional Integer $nonzero + * @return String + */ + function _random($bytes, $nonzero = false) + { + $temp = ''; + if ($nonzero) { + for ($i = 0; $i < $bytes; $i++) { + $temp.= chr(crypt_random(1, 255)); + } + } else { + $ints = ($bytes + 1) >> 2; + for ($i = 0; $i < $ints; $i++) { + $temp.= pack('N', crypt_random()); + } + $temp = substr($temp, 0, $bytes); + } + return $temp; + } + + /** + * Integer-to-Octet-String primitive + * + * See {@link http://tools.ietf.org/html/rfc3447#section-4.1 RFC3447#section-4.1}. + * + * @access private + * @param Math_BigInteger $x + * @param Integer $xLen + * @return String + */ + function _i2osp($x, $xLen) + { + $x = $x->toBytes(); + if (strlen($x) > $xLen) { + user_error('Integer too large', E_USER_NOTICE); + return false; + } + return str_pad($x, $xLen, chr(0), STR_PAD_LEFT); + } + + /** + * Octet-String-to-Integer primitive + * + * See {@link http://tools.ietf.org/html/rfc3447#section-4.2 RFC3447#section-4.2}. + * + * @access private + * @param String $x + * @return Math_BigInteger + */ + function _os2ip($x) + { + return new Math_BigInteger($x, 256); + } + + /** + * Exponentiate with or without Chinese Remainder Theorem + * + * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.1 RFC3447#section-5.1.2}. + * + * @access private + * @param Math_BigInteger $x + * @return Math_BigInteger + */ + function _exponentiate($x) + { + if (empty($this->primes) || empty($this->coefficients) || empty($this->exponents)) { + return $x->modPow($this->exponent, $this->modulus); + } + + $num_primes = count($this->primes); + + if (defined('CRYPT_RSA_DISABLE_BLINDING')) { + $m_i = array( + 1 => $x->modPow($this->exponents[1], $this->primes[1]), + 2 => $x->modPow($this->exponents[2], $this->primes[2]) + ); + $h = $m_i[1]->subtract($m_i[2]); + $h = $h->multiply($this->coefficients[2]); + list(, $h) = $h->divide($this->primes[1]); + $m = $m_i[2]->add($h->multiply($this->primes[2])); + + $r = $this->primes[1]; + for ($i = 3; $i <= $num_primes; $i++) { + $m_i = $x->modPow($this->exponents[$i], $this->primes[$i]); + + $r = $r->multiply($this->primes[$i - 1]); + + $h = $m_i->subtract($m); + $h = $h->multiply($this->coefficients[$i]); + list(, $h) = $h->divide($this->primes[$i]); + + $m = $m->add($r->multiply($h)); + } + } else { + $smallest = $this->primes[1]; + for ($i = 2; $i <= $num_primes; $i++) { + if ($smallest->compare($this->primes[$i]) > 0) { + $smallest = $this->primes[$i]; + } + } + + $one = new Math_BigInteger(1); + $one->setRandomGenerator('crypt_random'); + + $r = $one->random($one, $smallest->subtract($one)); + + $m_i = array( + 1 => $this->_blind($x, $r, 1), + 2 => $this->_blind($x, $r, 2) + ); + $h = $m_i[1]->subtract($m_i[2]); + $h = $h->multiply($this->coefficients[2]); + list(, $h) = $h->divide($this->primes[1]); + $m = $m_i[2]->add($h->multiply($this->primes[2])); + + $r = $this->primes[1]; + for ($i = 3; $i <= $num_primes; $i++) { + $m_i = $this->_blind($x, $r, $i); + + $r = $r->multiply($this->primes[$i - 1]); + + $h = $m_i->subtract($m); + $h = $h->multiply($this->coefficients[$i]); + list(, $h) = $h->divide($this->primes[$i]); + + $m = $m->add($r->multiply($h)); + } + } + + return $m; + } + + /** + * Performs RSA Blinding + * + * Protects against timing attacks by employing RSA Blinding. + * Returns $x->modPow($this->exponents[$i], $this->primes[$i]) + * + * @access private + * @param Math_BigInteger $x + * @param Math_BigInteger $r + * @param Integer $i + * @return Math_BigInteger + */ + function _blind($x, $r, $i) + { + $x = $x->multiply($r->modPow($this->publicExponent, $this->primes[$i])); + + $x = $x->modPow($this->exponents[$i], $this->primes[$i]); + + $r = $r->modInverse($this->primes[$i]); + $x = $x->multiply($r); + list(, $x) = $x->divide($this->primes[$i]); + + return $x; + } + + /** + * RSAEP + * + * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.1 RFC3447#section-5.1.1}. + * + * @access private + * @param Math_BigInteger $m + * @return Math_BigInteger + */ + function _rsaep($m) + { + if ($m->compare($this->zero) < 0 || $m->compare($this->modulus) > 0) { + user_error('Message representative out of range', E_USER_NOTICE); + return false; + } + return $this->_exponentiate($m); + } + + /** + * RSADP + * + * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.2 RFC3447#section-5.1.2}. + * + * @access private + * @param Math_BigInteger $c + * @return Math_BigInteger + */ + function _rsadp($c) + { + if ($c->compare($this->zero) < 0 || $c->compare($this->modulus) > 0) { + user_error('Ciphertext representative out of range', E_USER_NOTICE); + return false; + } + return $this->_exponentiate($c); + } + + /** + * RSASP1 + * + * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.1 RFC3447#section-5.2.1}. + * + * @access private + * @param Math_BigInteger $m + * @return Math_BigInteger + */ + function _rsasp1($m) + { + if ($m->compare($this->zero) < 0 || $m->compare($this->modulus) > 0) { + user_error('Message representative out of range', E_USER_NOTICE); + return false; + } + return $this->_exponentiate($m); + } + + /** + * RSAVP1 + * + * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.2 RFC3447#section-5.2.2}. + * + * @access private + * @param Math_BigInteger $s + * @return Math_BigInteger + */ + function _rsavp1($s) + { + if ($s->compare($this->zero) < 0 || $s->compare($this->modulus) > 0) { + user_error('Signature representative out of range', E_USER_NOTICE); + return false; + } + return $this->_exponentiate($s); + } + + /** + * MGF1 + * + * See {@link http://tools.ietf.org/html/rfc3447#appendix-B.2.1 RFC3447#appendix-B.2.1}. + * + * @access private + * @param String $mgfSeed + * @param Integer $mgfLen + * @return String + */ + function _mgf1($mgfSeed, $maskLen) + { + // if $maskLen would yield strings larger than 4GB, PKCS#1 suggests a "Mask too long" error be output. + + $t = ''; + $count = ceil($maskLen / $this->mgfHLen); + for ($i = 0; $i < $count; $i++) { + $c = pack('N', $i); + $t.= $this->mgfHash->hash($mgfSeed . $c); + } + + return substr($t, 0, $maskLen); + } + + /** + * RSAES-OAEP-ENCRYPT + * + * See {@link http://tools.ietf.org/html/rfc3447#section-7.1.1 RFC3447#section-7.1.1} and + * {http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding OAES}. + * + * @access private + * @param String $m + * @param String $l + * @return String + */ + function _rsaes_oaep_encrypt($m, $l = '') + { + $mLen = strlen($m); + + // Length checking + + // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error + // be output. + + if ($mLen > $this->k - 2 * $this->hLen - 2) { + user_error('Message too long', E_USER_NOTICE); + return false; + } + + // EME-OAEP encoding + + $lHash = $this->hash->hash($l); + $ps = str_repeat(chr(0), $this->k - $mLen - 2 * $this->hLen - 2); + $db = $lHash . $ps . chr(1) . $m; + $seed = $this->_random($this->hLen); + $dbMask = $this->_mgf1($seed, $this->k - $this->hLen - 1); + $maskedDB = $db ^ $dbMask; + $seedMask = $this->_mgf1($maskedDB, $this->hLen); + $maskedSeed = $seed ^ $seedMask; + $em = chr(0) . $maskedSeed . $maskedDB; + + // RSA encryption + + $m = $this->_os2ip($em); + $c = $this->_rsaep($m); + $c = $this->_i2osp($c, $this->k); + + // Output the ciphertext C + + return $c; + } + + /** + * RSAES-OAEP-DECRYPT + * + * See {@link http://tools.ietf.org/html/rfc3447#section-7.1.2 RFC3447#section-7.1.2}. The fact that the error + * messages aren't distinguishable from one another hinders debugging, but, to quote from RFC3447#section-7.1.2: + * + * Note. Care must be taken to ensure that an opponent cannot + * distinguish the different error conditions in Step 3.g, whether by + * error message or timing, or, more generally, learn partial + * information about the encoded message EM. Otherwise an opponent may + * be able to obtain useful information about the decryption of the + * ciphertext C, leading to a chosen-ciphertext attack such as the one + * observed by Manger [36]. + * + * As for $l... to quote from {@link http://tools.ietf.org/html/rfc3447#page-17 RFC3447#page-17}: + * + * Both the encryption and the decryption operations of RSAES-OAEP take + * the value of a label L as input. In this version of PKCS #1, L is + * the empty string; other uses of the label are outside the scope of + * this document. + * + * @access private + * @param String $c + * @param String $l + * @return String + */ + function _rsaes_oaep_decrypt($c, $l = '') + { + // Length checking + + // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error + // be output. + + if (strlen($c) != $this->k || $this->k < 2 * $this->hLen + 2) { + user_error('Decryption error', E_USER_NOTICE); + return false; + } + + // RSA decryption + + $c = $this->_os2ip($c); + $m = $this->_rsadp($c); + if ($m === false) { + user_error('Decryption error', E_USER_NOTICE); + return false; + } + $em = $this->_i2osp($m, $this->k); + + // EME-OAEP decoding + + $lHash = $this->hash->hash($l); + $y = ord($em[0]); + $maskedSeed = substr($em, 1, $this->hLen); + $maskedDB = substr($em, $this->hLen + 1); + $seedMask = $this->_mgf1($maskedDB, $this->hLen); + $seed = $maskedSeed ^ $seedMask; + $dbMask = $this->_mgf1($seed, $this->k - $this->hLen - 1); + $db = $maskedDB ^ $dbMask; + $lHash2 = substr($db, 0, $this->hLen); + $m = substr($db, $this->hLen); + if ($lHash != $lHash2) { + user_error('Decryption error', E_USER_NOTICE); + return false; + } + $m = ltrim($m, chr(0)); + if (ord($m[0]) != 1) { + user_error('Decryption error', E_USER_NOTICE); + return false; + } + + // Output the message M + + return substr($m, 1); + } + + /** + * RSAES-PKCS1-V1_5-ENCRYPT + * + * See {@link http://tools.ietf.org/html/rfc3447#section-7.2.1 RFC3447#section-7.2.1}. + * + * @access private + * @param String $m + * @return String + */ + function _rsaes_pkcs1_v1_5_encrypt($m) + { + $mLen = strlen($m); + + // Length checking + + if ($mLen > $this->k - 11) { + user_error('Message too long', E_USER_NOTICE); + return false; + } + + // EME-PKCS1-v1_5 encoding + + $ps = $this->_random($this->k - $mLen - 3, true); + $em = chr(0) . chr(2) . $ps . chr(0) . $m; + + // RSA encryption + $m = $this->_os2ip($em); + $c = $this->_rsaep($m); + $c = $this->_i2osp($c, $this->k); + + // Output the ciphertext C + + return $c; + } + + /** + * RSAES-PKCS1-V1_5-DECRYPT + * + * See {@link http://tools.ietf.org/html/rfc3447#section-7.2.2 RFC3447#section-7.2.2}. + * + * @access private + * @param String $c + * @return String + */ + function _rsaes_pkcs1_v1_5_decrypt($c) + { + // Length checking + + if (strlen($c) != $this->k) { // or if k < 11 + user_error('Decryption error', E_USER_NOTICE); + return false; + } + + // RSA decryption + + $c = $this->_os2ip($c); + $m = $this->_rsadp($c); + if ($m === false) { + user_error('Decryption error', E_USER_NOTICE); + return false; + } + $em = $this->_i2osp($m, $this->k); + + // EME-PKCS1-v1_5 decoding + + if (ord($em[0]) != 0 || ord($em[1]) != 2) { + user_error('Decryption error', E_USER_NOTICE); + return false; + } + + $ps = substr($em, 2, strpos($em, chr(0), 2) - 2); + $m = substr($em, strlen($ps) + 3); + + if (strlen($ps) < 8) { + user_error('Decryption error', E_USER_NOTICE); + return false; + } + + // Output M + + return $m; + } + + /** + * EMSA-PSS-ENCODE + * + * See {@link http://tools.ietf.org/html/rfc3447#section-9.1.1 RFC3447#section-9.1.1}. + * + * @access private + * @param String $m + * @param Integer $emBits + */ + function _emsa_pss_encode($m, $emBits) + { + // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error + // be output. + + $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8) + $sLen = $this->sLen == false ? $this->hLen : $this->sLen; + + $mHash = $this->hash->hash($m); + if ($emLen < $this->hLen + $sLen + 2) { + user_error('Encoding error', E_USER_NOTICE); + return false; + } + + $salt = $this->_random($sLen); + $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt; + $h = $this->hash->hash($m2); + $ps = str_repeat(chr(0), $emLen - $sLen - $this->hLen - 2); + $db = $ps . chr(1) . $salt; + $dbMask = $this->_mgf1($h, $emLen - $this->hLen - 1); + $maskedDB = $db ^ $dbMask; + $maskedDB[0] = ~chr(0xFF << ($emBits & 7)) & $maskedDB[0]; + $em = $maskedDB . $h . chr(0xBC); + + return $em; + } + + /** + * EMSA-PSS-VERIFY + * + * See {@link http://tools.ietf.org/html/rfc3447#section-9.1.2 RFC3447#section-9.1.2}. + * + * @access private + * @param String $m + * @param String $em + * @param Integer $emBits + * @return String + */ + function _emsa_pss_verify($m, $em, $emBits) + { + // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error + // be output. + + $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8); + $sLen = $this->sLen == false ? $this->hLen : $this->sLen; + + $mHash = $this->hash->hash($m); + if ($emLen < $this->hLen + $sLen + 2) { + return false; + } + + if ($em[strlen($em) - 1] != chr(0xBC)) { + return false; + } + + $maskedDB = substr($em, 0, $em - $this->hLen - 1); + $h = substr($em, $em - $this->hLen - 1, $this->hLen); + $temp = chr(0xFF << ($emBits & 7)); + if ((~$maskedDB[0] & $temp) != $temp) { + return false; + } + $dbMask = $this->_mgf1($h, $emLen - $this->hLen - 1); + $db = $maskedDB ^ $dbMask; + $db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0]; + $temp = $emLen - $this->hLen - $sLen - 2; + if (substr($db, 0, $temp) != str_repeat(chr(0), $temp) || ord($db[$temp]) != 1) { + return false; + } + $salt = substr($db, $temp + 1); // should be $sLen long + $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt; + $h2 = $this->hash->hash($m2); + return $h == $h2; + } + + /** + * RSASSA-PSS-SIGN + * + * See {@link http://tools.ietf.org/html/rfc3447#section-8.1.1 RFC3447#section-8.1.1}. + * + * @access private + * @param String $m + * @return String + */ + function _rsassa_pss_sign($m) + { + // EMSA-PSS encoding + + $em = $this->_emsa_pss_encode($m, 8 * $this->k - 1); + + // RSA signature + + $m = $this->_os2ip($em); + $s = $this->_rsasp1($m); + $s = $this->_i2osp($s, $this->k); + + // Output the signature S + + return $s; + } + + /** + * RSASSA-PSS-VERIFY + * + * See {@link http://tools.ietf.org/html/rfc3447#section-8.1.2 RFC3447#section-8.1.2}. + * + * @access private + * @param String $m + * @param String $s + * @return String + */ + function _rsassa_pss_verify($m, $s) + { + // Length checking + + if (strlen($s) != $this->k) { + user_error('Invalid signature', E_USER_NOTICE); + return false; + } + + // RSA verification + + $modBits = 8 * $this->k; + + $s2 = $this->_os2ip($s); + $m2 = $this->_rsavp1($s2); + if ($m2 === false) { + user_error('Invalid signature', E_USER_NOTICE); + return false; + } + $em = $this->_i2osp($m2, $modBits >> 3); + if ($em === false) { + user_error('Invalid signature', E_USER_NOTICE); + return false; + } + + // EMSA-PSS verification + + return $this->_emsa_pss_verify($m, $em, $modBits - 1); + } + + /** + * EMSA-PKCS1-V1_5-ENCODE + * + * See {@link http://tools.ietf.org/html/rfc3447#section-9.2 RFC3447#section-9.2}. + * + * @access private + * @param String $m + * @param Integer $emLen + * @return String + */ + function _emsa_pkcs1_v1_5_encode($m, $emLen) + { + $h = $this->hash->hash($m); + if ($h === false) { + return false; + } + + // see http://tools.ietf.org/html/rfc3447#page-43 + switch ($this->hashName) { + case 'md2': + $t = pack('H*', '3020300c06082a864886f70d020205000410'); + break; + case 'md5': + $t = pack('H*', '3020300c06082a864886f70d020505000410'); + break; + case 'sha1': + $t = pack('H*', '3021300906052b0e03021a05000414'); + break; + case 'sha256': + $t = pack('H*', '3031300d060960864801650304020105000420'); + break; + case 'sha384': + $t = pack('H*', '3041300d060960864801650304020205000430'); + break; + case 'sha512': + $t = pack('H*', '3051300d060960864801650304020305000440'); + } + $t.= $h; + $tLen = strlen($t); + + if ($emLen < $tLen + 11) { + user_error('Intended encoded message length too short', E_USER_NOTICE); + return false; + } + + $ps = str_repeat(chr(0xFF), $emLen - $tLen - 3); + + $em = "\0\1$ps\0$t"; + + return $em; + } + + /** + * RSASSA-PKCS1-V1_5-SIGN + * + * See {@link http://tools.ietf.org/html/rfc3447#section-8.2.1 RFC3447#section-8.2.1}. + * + * @access private + * @param String $m + * @return String + */ + function _rsassa_pkcs1_v1_5_sign($m) + { + // EMSA-PKCS1-v1_5 encoding + + $em = $this->_emsa_pkcs1_v1_5_encode($m, $this->k); + if ($em === false) { + user_error('RSA modulus too short', E_USER_NOTICE); + return false; + } + + // RSA signature + + $m = $this->_os2ip($em); + $s = $this->_rsasp1($m); + $s = $this->_i2osp($s, $this->k); + + // Output the signature S + + return $s; + } + + /** + * RSASSA-PKCS1-V1_5-VERIFY + * + * See {@link http://tools.ietf.org/html/rfc3447#section-8.2.2 RFC3447#section-8.2.2}. + * + * @access private + * @param String $m + * @return String + */ + function _rsassa_pkcs1_v1_5_verify($m, $s) + { + // Length checking + + if (strlen($s) != $this->k) { + user_error('Invalid signature', E_USER_NOTICE); + return false; + } + + // RSA verification + + $s = $this->_os2ip($s); + $m2 = $this->_rsavp1($s); + if ($m2 === false) { + user_error('Invalid signature', E_USER_NOTICE); + return false; + } + $em = $this->_i2osp($m2, $this->k); + if ($em === false) { + user_error('Invalid signature', E_USER_NOTICE); + return false; + } + + // EMSA-PKCS1-v1_5 encoding + + $em2 = $this->_emsa_pkcs1_v1_5_encode($m, $this->k); + if ($em2 === false) { + user_error('RSA modulus too short', E_USER_NOTICE); + return false; + } + + // Compare + + return $em === $em2; + } + + /** + * Set Encryption Mode + * + * Valid values include CRYPT_RSA_ENCRYPTION_OAEP and CRYPT_RSA_ENCRYPTION_PKCS1. + * + * @access public + * @param Integer $mode + */ + function setEncryptionMode($mode) + { + $this->encryptionMode = $mode; + } + + /** + * Set Signature Mode + * + * Valid values include CRYPT_RSA_SIGNATURE_PSS and CRYPT_RSA_SIGNATURE_PKCS1 + * + * @access public + * @param Integer $mode + */ + function setSignatureMode($mode) + { + $this->signatureMode = $mode; + } + + /** + * Encryption + * + * Both CRYPT_RSA_ENCRYPTION_OAEP and CRYPT_RSA_ENCRYPTION_PKCS1 both place limits on how long $plaintext can be. + * If $plaintext exceeds those limits it will be broken up so that it does and the resultant ciphertext's will + * be concatenated together. + * + * @see decrypt() + * @access public + * @param String $plaintext + * @return String + */ + function encrypt($plaintext) + { + switch ($this->encryptionMode) { + case CRYPT_RSA_ENCRYPTION_PKCS1: + $length = $this->k - 11; + if ($length <= 0) { + return false; + } + + $plaintext = str_split($plaintext, $length); + $ciphertext = ''; + foreach ($plaintext as $m) { + $ciphertext.= $this->_rsaes_pkcs1_v1_5_encrypt($m); + } + return $ciphertext; + //case CRYPT_RSA_ENCRYPTION_OAEP: + default: + $length = $this->k - 2 * $this->hLen - 2; + if ($length <= 0) { + return false; + } + + $plaintext = str_split($plaintext, $length); + $ciphertext = ''; + foreach ($plaintext as $m) { + $ciphertext.= $this->_rsaes_oaep_encrypt($m); + } + return $ciphertext; + } + } + + /** + * Decryption + * + * @see encrypt() + * @access public + * @param String $plaintext + * @return String + */ + function decrypt($ciphertext) + { + if ($this->k <= 0) { + return false; + } + + $ciphertext = str_split($ciphertext, $this->k); + $plaintext = ''; + + switch ($this->encryptionMode) { + case CRYPT_RSA_ENCRYPTION_PKCS1: + $decrypt = '_rsaes_pkcs1_v1_5_decrypt'; + break; + //case CRYPT_RSA_ENCRYPTION_OAEP: + default: + $decrypt = '_rsaes_oaep_decrypt'; + } + + foreach ($ciphertext as $c) { + $temp = $this->$decrypt($c); + if ($temp === false) { + return false; + } + $plaintext.= $temp; + } + + return $plaintext; + } + + /** + * Create a signature + * + * @see verify() + * @access public + * @param String $message + * @return String + */ + function sign($message) + { + if (empty($this->modulus) || empty($this->exponent)) { + return false; + } + + switch ($this->signatureMode) { + case CRYPT_RSA_SIGNATURE_PKCS1: + return $this->_rsassa_pkcs1_v1_5_sign($message); + //case CRYPT_RSA_SIGNATURE_PSS: + default: + return $this->_rsassa_pss_sign($message); + } + } + + /** + * Verifies a signature + * + * @see sign() + * @access public + * @param String $message + * @param String $signature + * @return Boolean + */ + function verify($message, $signature) + { + if (empty($this->modulus) || empty($this->exponent)) { + return false; + } + + switch ($this->signatureMode) { + case CRYPT_RSA_SIGNATURE_PKCS1: + return $this->_rsassa_pkcs1_v1_5_verify($message, $signature); + //case CRYPT_RSA_SIGNATURE_PSS: + default: + return $this->_rsassa_pss_verify($message, $signature); + } + } } \ No newline at end of file diff --git a/plugins/OStatus/extlib/Crypt/Random.php b/plugins/OStatus/extlib/Crypt/Random.php index fbb41074e..bfc24ca62 100644 --- a/plugins/OStatus/extlib/Crypt/Random.php +++ b/plugins/OStatus/extlib/Crypt/Random.php @@ -1,70 +1,125 @@ - - * - * - * - * LICENSE: This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, - * MA 02111-1307 USA - * - * @category Crypt - * @package Crypt_Random - * @author Jim Wigginton - * @copyright MMVII Jim Wigginton - * @license http://www.gnu.org/licenses/lgpl.txt - * @version $Id: Random.php,v 1.4 2008/05/21 05:15:32 terrafrost Exp $ - * @link http://phpseclib.sourceforge.net - */ - -/** - * Generate a random value. Feel free to replace this function with a cryptographically secure PRNG. - * - * @param optional Integer $min - * @param optional Integer $max - * @param optional String $randomness_path - * @return Integer - * @access public - */ -function crypt_random($min = 0, $max = 0x7FFFFFFF, $randomness_path = '/dev/urandom') -{ - static $seeded = false; - - if (!$seeded) { - $seeded = true; - if (file_exists($randomness_path)) { - $fp = fopen($randomness_path, 'r'); - $temp = unpack('Nint', fread($fp, 4)); - mt_srand($temp['int']); - fclose($fp); - } else { - list($sec, $usec) = explode(' ', microtime()); - mt_srand((float) $sec + ((float) $usec * 100000)); - } - } - - return mt_rand($min, $max); -} + + * + * + * + * LICENSE: This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * @category Crypt + * @package Crypt_Random + * @author Jim Wigginton + * @copyright MMVII Jim Wigginton + * @license http://www.gnu.org/licenses/lgpl.txt + * @version $Id: Random.php,v 1.6 2010/02/28 05:28:38 terrafrost Exp $ + * @link http://phpseclib.sourceforge.net + */ + +/** + * Generate a random value. + * + * On 32-bit machines, the largest distance that can exist between $min and $max is 2**31. + * If $min and $max are farther apart than that then the last ($max - range) numbers. + * + * Depending on how this is being used, it may be worth while to write a replacement. For example, + * a PHP-based web app that stores its data in an SQL database can collect more entropy than this function + * can. + * + * @param optional Integer $min + * @param optional Integer $max + * @return Integer + * @access public + */ +function crypt_random($min = 0, $max = 0x7FFFFFFF) +{ + if ($min == $max) { + return $min; + } + + // see http://en.wikipedia.org/wiki//dev/random + if (file_exists('/dev/urandom')) { + $fp = fopen('/dev/urandom', 'rb'); + extract(unpack('Nrandom', fread($fp, 4))); + fclose($fp); + + // say $min = 0 and $max = 3. if we didn't do abs() then we could have stuff like this: + // -4 % 3 + 0 = -1, even though -1 < $min + return abs($random) % ($max - $min) + $min; + } + + /* Prior to PHP 4.2.0, mt_srand() had to be called before mt_rand() could be called. + Prior to PHP 5.2.6, mt_rand()'s automatic seeding was subpar, as elaborated here: + + http://www.suspekt.org/2008/08/17/mt_srand-and-not-so-random-numbers/ + + The seeding routine is pretty much ripped from PHP's own internal GENERATE_SEED() macro: + + http://svn.php.net/viewvc/php/php-src/branches/PHP_5_3_2/ext/standard/php_rand.h?view=markup */ + if (version_compare(PHP_VERSION, '5.2.5', '<=')) { + static $seeded; + if (!isset($seeded)) { + $seeded = true; + mt_srand(fmod(time() * getmypid(), 0x7FFFFFFF) ^ fmod(1000000 * lcg_value(), 0x7FFFFFFF)); + } + } + + static $crypto; + + // The CSPRNG's Yarrow and Fortuna periodically reseed. This function can be reseeded by hitting F5 + // in the browser and reloading the page. + + if (!isset($crypto)) { + $key = $iv = ''; + for ($i = 0; $i < 8; $i++) { + $key.= pack('n', mt_rand(0, 0xFFFF)); + $iv .= pack('n', mt_rand(0, 0xFFFF)); + } + switch (true) { + case class_exists('Crypt_AES'): + $crypto = new Crypt_AES(CRYPT_AES_MODE_CTR); + break; + case class_exists('Crypt_TripleDES'): + $crypto = new Crypt_TripleDES(CRYPT_DES_MODE_CTR); + break; + case class_exists('Crypt_DES'): + $crypto = new Crypt_DES(CRYPT_DES_MODE_CTR); + break; + case class_exists('Crypt_RC4'): + $crypto = new Crypt_RC4(); + break; + default: + extract(unpack('Nrandom', pack('H*', sha1(mt_rand(0, 0x7FFFFFFF))))); + return abs($random) % ($max - $min) + $min; + } + $crypto->setKey($key); + $crypto->setIV($iv); + } + + extract(unpack('Nrandom', $crypto->encrypt("\0\0\0\0"))); + return abs($random) % ($max - $min) + $min; +} ?> \ No newline at end of file diff --git a/plugins/OStatus/extlib/Crypt/Rijndael.php b/plugins/OStatus/extlib/Crypt/Rijndael.php index 19bba83f3..3b5fd6a7d 100644 --- a/plugins/OStatus/extlib/Crypt/Rijndael.php +++ b/plugins/OStatus/extlib/Crypt/Rijndael.php @@ -1,1135 +1,1242 @@ - - * setKey('abcdefghijklmnop'); - * - * $size = 10 * 1024; - * $plaintext = ''; - * for ($i = 0; $i < $size; $i++) { - * $plaintext.= 'a'; - * } - * - * echo $rijndael->decrypt($rijndael->encrypt($plaintext)); - * ?> - * - * - * LICENSE: This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, - * MA 02111-1307 USA - * - * @category Crypt - * @package Crypt_Rijndael - * @author Jim Wigginton - * @copyright MMVIII Jim Wigginton - * @license http://www.gnu.org/licenses/lgpl.txt - * @version $Id: Rijndael.php,v 1.8 2009/11/23 19:06:07 terrafrost Exp $ - * @link http://phpseclib.sourceforge.net - */ - -/**#@+ - * @access public - * @see Crypt_Rijndael::encrypt() - * @see Crypt_Rijndael::decrypt() - */ -/** - * Encrypt / decrypt using the Electronic Code Book mode. - * - * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29 - */ -define('CRYPT_RIJNDAEL_MODE_ECB', 1); -/** - * Encrypt / decrypt using the Code Book Chaining mode. - * - * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29 - */ -define('CRYPT_RIJNDAEL_MODE_CBC', 2); -/**#@-*/ - -/**#@+ - * @access private - * @see Crypt_Rijndael::Crypt_Rijndael() - */ -/** - * Toggles the internal implementation - */ -define('CRYPT_RIJNDAEL_MODE_INTERNAL', 1); -/** - * Toggles the mcrypt implementation - */ -define('CRYPT_RIJNDAEL_MODE_MCRYPT', 2); -/**#@-*/ - -/** - * Pure-PHP implementation of Rijndael. - * - * @author Jim Wigginton - * @version 0.1.0 - * @access public - * @package Crypt_Rijndael - */ -class Crypt_Rijndael { - /** - * The Encryption Mode - * - * @see Crypt_Rijndael::Crypt_Rijndael() - * @var Integer - * @access private - */ - var $mode; - - /** - * The Key - * - * @see Crypt_Rijndael::setKey() - * @var String - * @access private - */ - var $key = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; - - /** - * The Initialization Vector - * - * @see Crypt_Rijndael::setIV() - * @var String - * @access private - */ - var $iv = ''; - - /** - * A "sliding" Initialization Vector - * - * @see Crypt_Rijndael::enableContinuousBuffer() - * @var String - * @access private - */ - var $encryptIV = ''; - - /** - * A "sliding" Initialization Vector - * - * @see Crypt_Rijndael::enableContinuousBuffer() - * @var String - * @access private - */ - var $decryptIV = ''; - - /** - * Continuous Buffer status - * - * @see Crypt_Rijndael::enableContinuousBuffer() - * @var Boolean - * @access private - */ - var $continuousBuffer = false; - - /** - * Padding status - * - * @see Crypt_Rijndael::enablePadding() - * @var Boolean - * @access private - */ - var $padding = true; - - /** - * Does the key schedule need to be (re)calculated? - * - * @see setKey() - * @see setBlockLength() - * @see setKeyLength() - * @var Boolean - * @access private - */ - var $changed = true; - - /** - * Has the key length explicitly been set or should it be derived from the key, itself? - * - * @see setKeyLength() - * @var Boolean - * @access private - */ - var $explicit_key_length = false; - - /** - * The Key Schedule - * - * @see _setup() - * @var Array - * @access private - */ - var $w; - - /** - * The Inverse Key Schedule - * - * @see _setup() - * @var Array - * @access private - */ - var $dw; - - /** - * The Block Length - * - * @see setBlockLength() - * @var Integer - * @access private - * @internal The max value is 32, the min value is 16. All valid values are multiples of 4. Exists in conjunction with - * $Nb because we need this value and not $Nb to pad strings appropriately. - */ - var $block_size = 16; - - /** - * The Block Length divided by 32 - * - * @see setBlockLength() - * @var Integer - * @access private - * @internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4. Exists in conjunction with $block_size - * because the encryption / decryption / key schedule creation requires this number and not $block_size. We could - * derive this from $block_size or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu - * of that, we'll just precompute it once. - * - */ - var $Nb = 4; - - /** - * The Key Length - * - * @see setKeyLength() - * @var Integer - * @access private - * @internal The max value is 256 / 8 = 32, the min value is 128 / 8 = 16. Exists in conjunction with $key_size - * because the encryption / decryption / key schedule creation requires this number and not $key_size. We could - * derive this from $key_size or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu - * of that, we'll just precompute it once. - */ - var $key_size = 16; - - /** - * The Key Length divided by 32 - * - * @see setKeyLength() - * @var Integer - * @access private - * @internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4 - */ - var $Nk = 4; - - /** - * The Number of Rounds - * - * @var Integer - * @access private - * @internal The max value is 14, the min value is 10. - */ - var $Nr; - - /** - * Shift offsets - * - * @var Array - * @access private - */ - var $c; - - /** - * Precomputed mixColumns table - * - * @see Crypt_Rijndael() - * @var Array - * @access private - */ - var $t0; - - /** - * Precomputed mixColumns table - * - * @see Crypt_Rijndael() - * @var Array - * @access private - */ - var $t1; - - /** - * Precomputed mixColumns table - * - * @see Crypt_Rijndael() - * @var Array - * @access private - */ - var $t2; - - /** - * Precomputed mixColumns table - * - * @see Crypt_Rijndael() - * @var Array - * @access private - */ - var $t3; - - /** - * Precomputed invMixColumns table - * - * @see Crypt_Rijndael() - * @var Array - * @access private - */ - var $dt0; - - /** - * Precomputed invMixColumns table - * - * @see Crypt_Rijndael() - * @var Array - * @access private - */ - var $dt1; - - /** - * Precomputed invMixColumns table - * - * @see Crypt_Rijndael() - * @var Array - * @access private - */ - var $dt2; - - /** - * Precomputed invMixColumns table - * - * @see Crypt_Rijndael() - * @var Array - * @access private - */ - var $dt3; - - /** - * Default Constructor. - * - * Determines whether or not the mcrypt extension should be used. $mode should only, at present, be - * CRYPT_RIJNDAEL_MODE_ECB or CRYPT_RIJNDAEL_MODE_CBC. If not explictly set, CRYPT_RIJNDAEL_MODE_CBC will be used. - * - * @param optional Integer $mode - * @return Crypt_Rijndael - * @access public - */ - function Crypt_Rijndael($mode = CRYPT_RIJNDAEL_MODE_CBC) - { - switch ($mode) { - case CRYPT_RIJNDAEL_MODE_ECB: - case CRYPT_RIJNDAEL_MODE_CBC: - $this->mode = $mode; - break; - default: - $this->mode = CRYPT_RIJNDAEL_MODE_CBC; - } - - // according to (section 5.2.1), - // precomputed tables can be used in the mixColumns phase. in that example, they're assigned t0...t3, so - // those are the names we'll use. - $this->t3 = array( - 0x6363A5C6, 0x7C7C84F8, 0x777799EE, 0x7B7B8DF6, 0xF2F20DFF, 0x6B6BBDD6, 0x6F6FB1DE, 0xC5C55491, - 0x30305060, 0x01010302, 0x6767A9CE, 0x2B2B7D56, 0xFEFE19E7, 0xD7D762B5, 0xABABE64D, 0x76769AEC, - 0xCACA458F, 0x82829D1F, 0xC9C94089, 0x7D7D87FA, 0xFAFA15EF, 0x5959EBB2, 0x4747C98E, 0xF0F00BFB, - 0xADADEC41, 0xD4D467B3, 0xA2A2FD5F, 0xAFAFEA45, 0x9C9CBF23, 0xA4A4F753, 0x727296E4, 0xC0C05B9B, - 0xB7B7C275, 0xFDFD1CE1, 0x9393AE3D, 0x26266A4C, 0x36365A6C, 0x3F3F417E, 0xF7F702F5, 0xCCCC4F83, - 0x34345C68, 0xA5A5F451, 0xE5E534D1, 0xF1F108F9, 0x717193E2, 0xD8D873AB, 0x31315362, 0x15153F2A, - 0x04040C08, 0xC7C75295, 0x23236546, 0xC3C35E9D, 0x18182830, 0x9696A137, 0x05050F0A, 0x9A9AB52F, - 0x0707090E, 0x12123624, 0x80809B1B, 0xE2E23DDF, 0xEBEB26CD, 0x2727694E, 0xB2B2CD7F, 0x75759FEA, - 0x09091B12, 0x83839E1D, 0x2C2C7458, 0x1A1A2E34, 0x1B1B2D36, 0x6E6EB2DC, 0x5A5AEEB4, 0xA0A0FB5B, - 0x5252F6A4, 0x3B3B4D76, 0xD6D661B7, 0xB3B3CE7D, 0x29297B52, 0xE3E33EDD, 0x2F2F715E, 0x84849713, - 0x5353F5A6, 0xD1D168B9, 0x00000000, 0xEDED2CC1, 0x20206040, 0xFCFC1FE3, 0xB1B1C879, 0x5B5BEDB6, - 0x6A6ABED4, 0xCBCB468D, 0xBEBED967, 0x39394B72, 0x4A4ADE94, 0x4C4CD498, 0x5858E8B0, 0xCFCF4A85, - 0xD0D06BBB, 0xEFEF2AC5, 0xAAAAE54F, 0xFBFB16ED, 0x4343C586, 0x4D4DD79A, 0x33335566, 0x85859411, - 0x4545CF8A, 0xF9F910E9, 0x02020604, 0x7F7F81FE, 0x5050F0A0, 0x3C3C4478, 0x9F9FBA25, 0xA8A8E34B, - 0x5151F3A2, 0xA3A3FE5D, 0x4040C080, 0x8F8F8A05, 0x9292AD3F, 0x9D9DBC21, 0x38384870, 0xF5F504F1, - 0xBCBCDF63, 0xB6B6C177, 0xDADA75AF, 0x21216342, 0x10103020, 0xFFFF1AE5, 0xF3F30EFD, 0xD2D26DBF, - 0xCDCD4C81, 0x0C0C1418, 0x13133526, 0xECEC2FC3, 0x5F5FE1BE, 0x9797A235, 0x4444CC88, 0x1717392E, - 0xC4C45793, 0xA7A7F255, 0x7E7E82FC, 0x3D3D477A, 0x6464ACC8, 0x5D5DE7BA, 0x19192B32, 0x737395E6, - 0x6060A0C0, 0x81819819, 0x4F4FD19E, 0xDCDC7FA3, 0x22226644, 0x2A2A7E54, 0x9090AB3B, 0x8888830B, - 0x4646CA8C, 0xEEEE29C7, 0xB8B8D36B, 0x14143C28, 0xDEDE79A7, 0x5E5EE2BC, 0x0B0B1D16, 0xDBDB76AD, - 0xE0E03BDB, 0x32325664, 0x3A3A4E74, 0x0A0A1E14, 0x4949DB92, 0x06060A0C, 0x24246C48, 0x5C5CE4B8, - 0xC2C25D9F, 0xD3D36EBD, 0xACACEF43, 0x6262A6C4, 0x9191A839, 0x9595A431, 0xE4E437D3, 0x79798BF2, - 0xE7E732D5, 0xC8C8438B, 0x3737596E, 0x6D6DB7DA, 0x8D8D8C01, 0xD5D564B1, 0x4E4ED29C, 0xA9A9E049, - 0x6C6CB4D8, 0x5656FAAC, 0xF4F407F3, 0xEAEA25CF, 0x6565AFCA, 0x7A7A8EF4, 0xAEAEE947, 0x08081810, - 0xBABAD56F, 0x787888F0, 0x25256F4A, 0x2E2E725C, 0x1C1C2438, 0xA6A6F157, 0xB4B4C773, 0xC6C65197, - 0xE8E823CB, 0xDDDD7CA1, 0x74749CE8, 0x1F1F213E, 0x4B4BDD96, 0xBDBDDC61, 0x8B8B860D, 0x8A8A850F, - 0x707090E0, 0x3E3E427C, 0xB5B5C471, 0x6666AACC, 0x4848D890, 0x03030506, 0xF6F601F7, 0x0E0E121C, - 0x6161A3C2, 0x35355F6A, 0x5757F9AE, 0xB9B9D069, 0x86869117, 0xC1C15899, 0x1D1D273A, 0x9E9EB927, - 0xE1E138D9, 0xF8F813EB, 0x9898B32B, 0x11113322, 0x6969BBD2, 0xD9D970A9, 0x8E8E8907, 0x9494A733, - 0x9B9BB62D, 0x1E1E223C, 0x87879215, 0xE9E920C9, 0xCECE4987, 0x5555FFAA, 0x28287850, 0xDFDF7AA5, - 0x8C8C8F03, 0xA1A1F859, 0x89898009, 0x0D0D171A, 0xBFBFDA65, 0xE6E631D7, 0x4242C684, 0x6868B8D0, - 0x4141C382, 0x9999B029, 0x2D2D775A, 0x0F0F111E, 0xB0B0CB7B, 0x5454FCA8, 0xBBBBD66D, 0x16163A2C - ); - - $this->dt3 = array( - 0xF4A75051, 0x4165537E, 0x17A4C31A, 0x275E963A, 0xAB6BCB3B, 0x9D45F11F, 0xFA58ABAC, 0xE303934B, - 0x30FA5520, 0x766DF6AD, 0xCC769188, 0x024C25F5, 0xE5D7FC4F, 0x2ACBD7C5, 0x35448026, 0x62A38FB5, - 0xB15A49DE, 0xBA1B6725, 0xEA0E9845, 0xFEC0E15D, 0x2F7502C3, 0x4CF01281, 0x4697A38D, 0xD3F9C66B, - 0x8F5FE703, 0x929C9515, 0x6D7AEBBF, 0x5259DA95, 0xBE832DD4, 0x7421D358, 0xE0692949, 0xC9C8448E, - 0xC2896A75, 0x8E7978F4, 0x583E6B99, 0xB971DD27, 0xE14FB6BE, 0x88AD17F0, 0x20AC66C9, 0xCE3AB47D, - 0xDF4A1863, 0x1A3182E5, 0x51336097, 0x537F4562, 0x6477E0B1, 0x6BAE84BB, 0x81A01CFE, 0x082B94F9, - 0x48685870, 0x45FD198F, 0xDE6C8794, 0x7BF8B752, 0x73D323AB, 0x4B02E272, 0x1F8F57E3, 0x55AB2A66, - 0xEB2807B2, 0xB5C2032F, 0xC57B9A86, 0x3708A5D3, 0x2887F230, 0xBFA5B223, 0x036ABA02, 0x16825CED, - 0xCF1C2B8A, 0x79B492A7, 0x07F2F0F3, 0x69E2A14E, 0xDAF4CD65, 0x05BED506, 0x34621FD1, 0xA6FE8AC4, - 0x2E539D34, 0xF355A0A2, 0x8AE13205, 0xF6EB75A4, 0x83EC390B, 0x60EFAA40, 0x719F065E, 0x6E1051BD, - 0x218AF93E, 0xDD063D96, 0x3E05AEDD, 0xE6BD464D, 0x548DB591, 0xC45D0571, 0x06D46F04, 0x5015FF60, - 0x98FB2419, 0xBDE997D6, 0x4043CC89, 0xD99E7767, 0xE842BDB0, 0x898B8807, 0x195B38E7, 0xC8EEDB79, - 0x7C0A47A1, 0x420FE97C, 0x841EC9F8, 0x00000000, 0x80868309, 0x2BED4832, 0x1170AC1E, 0x5A724E6C, - 0x0EFFFBFD, 0x8538560F, 0xAED51E3D, 0x2D392736, 0x0FD9640A, 0x5CA62168, 0x5B54D19B, 0x362E3A24, - 0x0A67B10C, 0x57E70F93, 0xEE96D2B4, 0x9B919E1B, 0xC0C54F80, 0xDC20A261, 0x774B695A, 0x121A161C, - 0x93BA0AE2, 0xA02AE5C0, 0x22E0433C, 0x1B171D12, 0x090D0B0E, 0x8BC7ADF2, 0xB6A8B92D, 0x1EA9C814, - 0xF1198557, 0x75074CAF, 0x99DDBBEE, 0x7F60FDA3, 0x01269FF7, 0x72F5BC5C, 0x663BC544, 0xFB7E345B, - 0x4329768B, 0x23C6DCCB, 0xEDFC68B6, 0xE4F163B8, 0x31DCCAD7, 0x63851042, 0x97224013, 0xC6112084, - 0x4A247D85, 0xBB3DF8D2, 0xF93211AE, 0x29A16DC7, 0x9E2F4B1D, 0xB230F3DC, 0x8652EC0D, 0xC1E3D077, - 0xB3166C2B, 0x70B999A9, 0x9448FA11, 0xE9642247, 0xFC8CC4A8, 0xF03F1AA0, 0x7D2CD856, 0x3390EF22, - 0x494EC787, 0x38D1C1D9, 0xCAA2FE8C, 0xD40B3698, 0xF581CFA6, 0x7ADE28A5, 0xB78E26DA, 0xADBFA43F, - 0x3A9DE42C, 0x78920D50, 0x5FCC9B6A, 0x7E466254, 0x8D13C2F6, 0xD8B8E890, 0x39F75E2E, 0xC3AFF582, - 0x5D80BE9F, 0xD0937C69, 0xD52DA96F, 0x2512B3CF, 0xAC993BC8, 0x187DA710, 0x9C636EE8, 0x3BBB7BDB, - 0x267809CD, 0x5918F46E, 0x9AB701EC, 0x4F9AA883, 0x956E65E6, 0xFFE67EAA, 0xBCCF0821, 0x15E8E6EF, - 0xE79BD9BA, 0x6F36CE4A, 0x9F09D4EA, 0xB07CD629, 0xA4B2AF31, 0x3F23312A, 0xA59430C6, 0xA266C035, - 0x4EBC3774, 0x82CAA6FC, 0x90D0B0E0, 0xA7D81533, 0x04984AF1, 0xECDAF741, 0xCD500E7F, 0x91F62F17, - 0x4DD68D76, 0xEFB04D43, 0xAA4D54CC, 0x9604DFE4, 0xD1B5E39E, 0x6A881B4C, 0x2C1FB8C1, 0x65517F46, - 0x5EEA049D, 0x8C355D01, 0x877473FA, 0x0B412EFB, 0x671D5AB3, 0xDBD25292, 0x105633E9, 0xD647136D, - 0xD7618C9A, 0xA10C7A37, 0xF8148E59, 0x133C89EB, 0xA927EECE, 0x61C935B7, 0x1CE5EDE1, 0x47B13C7A, - 0xD2DF599C, 0xF2733F55, 0x14CE7918, 0xC737BF73, 0xF7CDEA53, 0xFDAA5B5F, 0x3D6F14DF, 0x44DB8678, - 0xAFF381CA, 0x68C43EB9, 0x24342C38, 0xA3405FC2, 0x1DC37216, 0xE2250CBC, 0x3C498B28, 0x0D9541FF, - 0xA8017139, 0x0CB3DE08, 0xB4E49CD8, 0x56C19064, 0xCB84617B, 0x32B670D5, 0x6C5C7448, 0xB85742D0 - ); - - for ($i = 0; $i < 256; $i++) { - $this->t2[$i << 8] = (($this->t3[$i] << 8) & 0xFFFFFF00) | (($this->t3[$i] >> 24) & 0x000000FF); - $this->t1[$i << 16] = (($this->t3[$i] << 16) & 0xFFFF0000) | (($this->t3[$i] >> 16) & 0x0000FFFF); - $this->t0[$i << 24] = (($this->t3[$i] << 24) & 0xFF000000) | (($this->t3[$i] >> 8) & 0x00FFFFFF); - - $this->dt2[$i << 8] = (($this->dt3[$i] << 8) & 0xFFFFFF00) | (($this->dt3[$i] >> 24) & 0x000000FF); - $this->dt1[$i << 16] = (($this->dt3[$i] << 16) & 0xFFFF0000) | (($this->dt3[$i] >> 16) & 0x0000FFFF); - $this->dt0[$i << 24] = (($this->dt3[$i] << 24) & 0xFF000000) | (($this->dt3[$i] >> 8) & 0x00FFFFFF); - } - } - - /** - * Sets the key. - * - * Keys can be of any length. Rijndael, itself, requires the use of a key that's between 128-bits and 256-bits long and - * whose length is a multiple of 32. If the key is less than 256-bits and the key length isn't set, we round the length - * up to the closest valid key length, padding $key with null bytes. If the key is more than 256-bits, we trim the - * excess bits. - * - * If the key is not explicitly set, it'll be assumed to be all null bytes. - * - * @access public - * @param String $key - */ - function setKey($key) - { - $this->key = $key; - $this->changed = true; - } - - /** - * Sets the initialization vector. (optional) - * - * SetIV is not required when CRYPT_RIJNDAEL_MODE_ECB is being used. If not explictly set, it'll be assumed - * to be all zero's. - * - * @access public - * @param String $iv - */ - function setIV($iv) - { - $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($iv, 0, $this->block_size), $this->block_size, chr(0));; - } - - /** - * Sets the key length - * - * Valid key lengths are 128, 160, 192, 224, and 256. If the length is less than 128, it will be rounded up to - * 128. If the length is greater then 128 and invalid, it will be rounded down to the closest valid amount. - * - * @access public - * @param Integer $length - */ - function setKeyLength($length) - { - $length >>= 5; - if ($length > 8) { - $length = 8; - } else if ($length < 4) { - $length = 4; - } - $this->Nk = $length; - $this->key_size = $length << 2; - - $this->explicit_key_length = true; - $this->changed = true; - } - - /** - * Sets the block length - * - * Valid block lengths are 128, 160, 192, 224, and 256. If the length is less than 128, it will be rounded up to - * 128. If the length is greater then 128 and invalid, it will be rounded down to the closest valid amount. - * - * @access public - * @param Integer $length - */ - function setBlockLength($length) - { - $length >>= 5; - if ($length > 8) { - $length = 8; - } else if ($length < 4) { - $length = 4; - } - $this->Nb = $length; - $this->block_size = $length << 2; - $this->changed = true; - } - - /** - * Encrypts a message. - * - * $plaintext will be padded with additional bytes such that it's length is a multiple of the block size. Other Rjindael - * implementations may or may not pad in the same manner. Other common approaches to padding and the reasons why it's - * necessary are discussed in the following - * URL: - * - * {@link http://www.di-mgt.com.au/cryptopad.html http://www.di-mgt.com.au/cryptopad.html} - * - * An alternative to padding is to, separately, send the length of the file. This is what SSH, in fact, does. - * strlen($plaintext) will still need to be a multiple of 8, however, arbitrary values can be added to make it that - * length. - * - * @see Crypt_Rijndael::decrypt() - * @access public - * @param String $plaintext - */ - function encrypt($plaintext) - { - $this->_setup(); - $plaintext = $this->_pad($plaintext); - - $ciphertext = ''; - switch ($this->mode) { - case CRYPT_RIJNDAEL_MODE_ECB: - for ($i = 0; $i < strlen($plaintext); $i+=$this->block_size) { - $ciphertext.= $this->_encryptBlock(substr($plaintext, $i, $this->block_size)); - } - break; - case CRYPT_RIJNDAEL_MODE_CBC: - $xor = $this->encryptIV; - for ($i = 0; $i < strlen($plaintext); $i+=$this->block_size) { - $block = substr($plaintext, $i, $this->block_size); - $block = $this->_encryptBlock($block ^ $xor); - $xor = $block; - $ciphertext.= $block; - } - if ($this->continuousBuffer) { - $this->encryptIV = $xor; - } - } - - return $ciphertext; - } - - /** - * Decrypts a message. - * - * If strlen($ciphertext) is not a multiple of the block size, null bytes will be added to the end of the string until - * it is. - * - * @see Crypt_Rijndael::encrypt() - * @access public - * @param String $ciphertext - */ - function decrypt($ciphertext) - { - $this->_setup(); - // we pad with chr(0) since that's what mcrypt_generic does. to quote from http://php.net/function.mcrypt-generic : - // "The data is padded with "\0" to make sure the length of the data is n * blocksize." - $ciphertext = str_pad($ciphertext, (strlen($ciphertext) + $this->block_size - 1) % $this->block_size, chr(0)); - - $plaintext = ''; - switch ($this->mode) { - case CRYPT_RIJNDAEL_MODE_ECB: - for ($i = 0; $i < strlen($ciphertext); $i+=$this->block_size) { - $plaintext.= $this->_decryptBlock(substr($ciphertext, $i, $this->block_size)); - } - break; - case CRYPT_RIJNDAEL_MODE_CBC: - $xor = $this->decryptIV; - for ($i = 0; $i < strlen($ciphertext); $i+=$this->block_size) { - $block = substr($ciphertext, $i, $this->block_size); - $plaintext.= $this->_decryptBlock($block) ^ $xor; - $xor = $block; - } - if ($this->continuousBuffer) { - $this->decryptIV = $xor; - } - } - - return $this->_unpad($plaintext); - } - - /** - * Encrypts a block - * - * @access private - * @param String $in - * @return String - */ - function _encryptBlock($in) - { - $state = array(); - $words = unpack('N*word', $in); - - // addRoundKey - foreach ($words as $word) { - $state[] = $word ^ $this->w[0][count($state)]; - } - - // fips-197.pdf#page=19, "Figure 5. Pseudo Code for the Cipher", states that this loop has four components - - // subBytes, shiftRows, mixColumns, and addRoundKey. fips-197.pdf#page=30, "Implementation Suggestions Regarding - // Various Platforms" suggests that performs enhanced implementations are described in Rijndael-ammended.pdf. - // Rijndael-ammended.pdf#page=20, "Implementation aspects / 32-bit processor", discusses such an optimization. - // Unfortunately, the description given there is not quite correct. Per aes.spec.v316.pdf#page=19 [1], - // equation (7.4.7) is supposed to use addition instead of subtraction, so we'll do that here, as well. - - // [1] http://fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.v316.pdf - $temp = array(); - for ($round = 1; $round < $this->Nr; $round++) { - $i = 0; // $this->c[0] == 0 - $j = $this->c[1]; - $k = $this->c[2]; - $l = $this->c[3]; - - while ($i < $this->Nb) { - $temp[$i] = $this->t0[$state[$i] & 0xFF000000] ^ - $this->t1[$state[$j] & 0x00FF0000] ^ - $this->t2[$state[$k] & 0x0000FF00] ^ - $this->t3[$state[$l] & 0x000000FF] ^ - $this->w[$round][$i]; - $i++; - $j = ($j + 1) % $this->Nb; - $k = ($k + 1) % $this->Nb; - $l = ($l + 1) % $this->Nb; - } - - for ($i = 0; $i < $this->Nb; $i++) { - $state[$i] = $temp[$i]; - } - } - - // subWord - for ($i = 0; $i < $this->Nb; $i++) { - $state[$i] = $this->_subWord($state[$i]); - } - - // shiftRows + addRoundKey - $i = 0; // $this->c[0] == 0 - $j = $this->c[1]; - $k = $this->c[2]; - $l = $this->c[3]; - while ($i < $this->Nb) { - $temp[$i] = ($state[$i] & 0xFF000000) ^ - ($state[$j] & 0x00FF0000) ^ - ($state[$k] & 0x0000FF00) ^ - ($state[$l] & 0x000000FF) ^ - $this->w[$this->Nr][$i]; - $i++; - $j = ($j + 1) % $this->Nb; - $k = ($k + 1) % $this->Nb; - $l = ($l + 1) % $this->Nb; - } - $state = $temp; - - array_unshift($state, 'N*'); - - return call_user_func_array('pack', $state); - } - - /** - * Decrypts a block - * - * @access private - * @param String $in - * @return String - */ - function _decryptBlock($in) - { - $state = array(); - $words = unpack('N*word', $in); - - // addRoundKey - foreach ($words as $word) { - $state[] = $word ^ $this->dw[0][count($state)]; - } - - $temp = array(); - for ($round = $this->Nr - 1; $round > 0; $round--) { - $i = 0; // $this->c[0] == 0 - $j = $this->Nb - $this->c[1]; - $k = $this->Nb - $this->c[2]; - $l = $this->Nb - $this->c[3]; - - while ($i < $this->Nb) { - $temp[$i] = $this->dt0[$state[$i] & 0xFF000000] ^ - $this->dt1[$state[$j] & 0x00FF0000] ^ - $this->dt2[$state[$k] & 0x0000FF00] ^ - $this->dt3[$state[$l] & 0x000000FF] ^ - $this->dw[$round][$i]; - $i++; - $j = ($j + 1) % $this->Nb; - $k = ($k + 1) % $this->Nb; - $l = ($l + 1) % $this->Nb; - } - - for ($i = 0; $i < $this->Nb; $i++) { - $state[$i] = $temp[$i]; - } - } - - // invShiftRows + invSubWord + addRoundKey - $i = 0; // $this->c[0] == 0 - $j = $this->Nb - $this->c[1]; - $k = $this->Nb - $this->c[2]; - $l = $this->Nb - $this->c[3]; - - while ($i < $this->Nb) { - $temp[$i] = $this->dw[0][$i] ^ - $this->_invSubWord(($state[$i] & 0xFF000000) | - ($state[$j] & 0x00FF0000) | - ($state[$k] & 0x0000FF00) | - ($state[$l] & 0x000000FF)); - $i++; - $j = ($j + 1) % $this->Nb; - $k = ($k + 1) % $this->Nb; - $l = ($l + 1) % $this->Nb; - } - - $state = $temp; - - array_unshift($state, 'N*'); - - return call_user_func_array('pack', $state); - } - - /** - * Setup Rijndael - * - * Validates all the variables and calculates $Nr - the number of rounds that need to be performed - and $w - the key - * key schedule. - * - * @access private - */ - function _setup() - { - // Each number in $rcon is equal to the previous number multiplied by two in Rijndael's finite field. - // See http://en.wikipedia.org/wiki/Finite_field_arithmetic#Multiplicative_inverse - static $rcon = array(0, - 0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, - 0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000, - 0x6C000000, 0xD8000000, 0xAB000000, 0x4D000000, 0x9A000000, - 0x2F000000, 0x5E000000, 0xBC000000, 0x63000000, 0xC6000000, - 0x97000000, 0x35000000, 0x6A000000, 0xD4000000, 0xB3000000, - 0x7D000000, 0xFA000000, 0xEF000000, 0xC5000000, 0x91000000 - ); - - if (!$this->changed) { - return; - } - - if (!$this->explicit_key_length) { - // we do >> 2, here, and not >> 5, as we do above, since strlen($this->key) tells us the number of bytes - not bits - $length = strlen($this->key) >> 2; - if ($length > 8) { - $length = 8; - } else if ($length < 4) { - $length = 4; - } - $this->Nk = $length; - $this->key_size = $length << 2; - } - - $this->key = str_pad(substr($this->key, 0, $this->key_size), $this->key_size, chr(0)); - $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($this->iv, 0, $this->block_size), $this->block_size, chr(0)); - - // see Rijndael-ammended.pdf#page=44 - $this->Nr = max($this->Nk, $this->Nb) + 6; - - // shift offsets for Nb = 5, 7 are defined in Rijndael-ammended.pdf#page=44, - // "Table 8: Shift offsets in Shiftrow for the alternative block lengths" - // shift offsets for Nb = 4, 6, 8 are defined in Rijndael-ammended.pdf#page=14, - // "Table 2: Shift offsets for different block lengths" - switch ($this->Nb) { - case 4: - case 5: - case 6: - $this->c = array(0, 1, 2, 3); - break; - case 7: - $this->c = array(0, 1, 2, 4); - break; - case 8: - $this->c = array(0, 1, 3, 4); - } - - $key = $this->key; - - $w = array_values(unpack('N*words', $key)); - - $length = $this->Nb * ($this->Nr + 1); - for ($i = $this->Nk; $i < $length; $i++) { - $temp = $w[$i - 1]; - if ($i % $this->Nk == 0) { - // according to , "the size of an integer is platform-dependent". - // on a 32-bit machine, it's 32-bits, and on a 64-bit machine, it's 64-bits. on a 32-bit machine, - // 0xFFFFFFFF << 8 == 0xFFFFFF00, but on a 64-bit machine, it equals 0xFFFFFFFF00. as such, doing 'and' - // with 0xFFFFFFFF (or 0xFFFFFF00) on a 32-bit machine is unnecessary, but on a 64-bit machine, it is. - $temp = (($temp << 8) & 0xFFFFFF00) | (($temp >> 24) & 0x000000FF); // rotWord - $temp = $this->_subWord($temp) ^ $rcon[$i / $this->Nk]; - } else if ($this->Nk > 6 && $i % $this->Nk == 4) { - $temp = $this->_subWord($temp); - } - $w[$i] = $w[$i - $this->Nk] ^ $temp; - } - - // convert the key schedule from a vector of $Nb * ($Nr + 1) length to a matrix with $Nr + 1 rows and $Nb columns - // and generate the inverse key schedule. more specifically, - // according to (section 5.3.3), - // "The key expansion for the Inverse Cipher is defined as follows: - // 1. Apply the Key Expansion. - // 2. Apply InvMixColumn to all Round Keys except the first and the last one." - // also, see fips-197.pdf#page=27, "5.3.5 Equivalent Inverse Cipher" - $temp = array(); - for ($i = $row = $col = 0; $i < $length; $i++, $col++) { - if ($col == $this->Nb) { - if ($row == 0) { - $this->dw[0] = $this->w[0]; - } else { - // subWord + invMixColumn + invSubWord = invMixColumn - $j = 0; - while ($j < $this->Nb) { - $dw = $this->_subWord($this->w[$row][$j]); - $temp[$j] = $this->dt0[$dw & 0xFF000000] ^ - $this->dt1[$dw & 0x00FF0000] ^ - $this->dt2[$dw & 0x0000FF00] ^ - $this->dt3[$dw & 0x000000FF]; - $j++; - } - $this->dw[$row] = $temp; - } - - $col = 0; - $row++; - } - $this->w[$row][$col] = $w[$i]; - } - - $this->dw[$row] = $this->w[$row]; - - $this->changed = false; - } - - /** - * Performs S-Box substitutions - * - * @access private - */ - function _subWord($word) - { - static $sbox0, $sbox1, $sbox2, $sbox3; - - if (empty($sbox0)) { - $sbox0 = array( - 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, - 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, - 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, - 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, - 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, - 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, - 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, - 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, - 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, - 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, - 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, - 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, - 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, - 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, - 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, - 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16 - ); - - $sbox1 = array(); - $sbox2 = array(); - $sbox3 = array(); - - for ($i = 0; $i < 256; $i++) { - $sbox1[$i << 8] = $sbox0[$i] << 8; - $sbox2[$i << 16] = $sbox0[$i] << 16; - $sbox3[$i << 24] = $sbox0[$i] << 24; - } - } - - return $sbox0[$word & 0x000000FF] | - $sbox1[$word & 0x0000FF00] | - $sbox2[$word & 0x00FF0000] | - $sbox3[$word & 0xFF000000]; - } - - /** - * Performs inverse S-Box substitutions - * - * @access private - */ - function _invSubWord($word) - { - static $sbox0, $sbox1, $sbox2, $sbox3; - - if (empty($sbox0)) { - $sbox0 = array( - 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, - 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, - 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, - 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, - 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, - 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, - 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06, - 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, - 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, - 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, - 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, - 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, - 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, - 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, - 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, - 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D - ); - - $sbox1 = array(); - $sbox2 = array(); - $sbox3 = array(); - - for ($i = 0; $i < 256; $i++) { - $sbox1[$i << 8] = $sbox0[$i] << 8; - $sbox2[$i << 16] = $sbox0[$i] << 16; - $sbox3[$i << 24] = $sbox0[$i] << 24; - } - } - - return $sbox0[$word & 0x000000FF] | - $sbox1[$word & 0x0000FF00] | - $sbox2[$word & 0x00FF0000] | - $sbox3[$word & 0xFF000000]; - } - - /** - * Pad "packets". - * - * Rijndael works by encrypting between sixteen and thirty-two bytes at a time, provided that number is also a multiple - * of four. If you ever need to encrypt or decrypt something that isn't of the proper length, it becomes necessary to - * pad the input so that it is of the proper length. - * - * Padding is enabled by default. Sometimes, however, it is undesirable to pad strings. Such is the case in SSH, - * where "packets" are padded with random bytes before being encrypted. Unpad these packets and you risk stripping - * away characters that shouldn't be stripped away. (SSH knows how many bytes are added because the length is - * transmitted separately) - * - * @see Crypt_Rijndael::disablePadding() - * @access public - */ - function enablePadding() - { - $this->padding = true; - } - - /** - * Do not pad packets. - * - * @see Crypt_Rijndael::enablePadding() - * @access public - */ - function disablePadding() - { - $this->padding = false; - } - - /** - * Pads a string - * - * Pads a string using the RSA PKCS padding standards so that its length is a multiple of the blocksize. - * $block_size - (strlen($text) % $block_size) bytes are added, each of which is equal to - * chr($block_size - (strlen($text) % $block_size) - * - * If padding is disabled and $text is not a multiple of the blocksize, the string will be padded regardless - * and padding will, hence forth, be enabled. - * - * @see Crypt_Rijndael::_unpad() - * @access private - */ - function _pad($text) - { - $length = strlen($text); - - if (!$this->padding) { - if ($length % $this->block_size == 0) { - return $text; - } else { - user_error("The plaintext's length ($length) is not a multiple of the block size ({$this->block_size})", E_USER_NOTICE); - $this->padding = true; - } - } - - $pad = $this->block_size - ($length % $this->block_size); - - return str_pad($text, $length + $pad, chr($pad)); - } - - /** - * Unpads a string. - * - * If padding is enabled and the reported padding length is invalid, padding will be, hence forth, disabled. - * - * @see Crypt_Rijndael::_pad() - * @access private - */ - function _unpad($text) - { - if (!$this->padding) { - return $text; - } - - $length = ord($text[strlen($text) - 1]); - - if (!$length || $length > $this->block_size) { - user_error("The number of bytes reported as being padded ($length) is invalid (block size = {$this->block_size})", E_USER_NOTICE); - $this->padding = false; - return $text; - } - - return substr($text, 0, -$length); - } - - /** - * Treat consecutive "packets" as if they are a continuous buffer. - * - * Say you have a 32-byte plaintext $plaintext. Using the default behavior, the two following code snippets - * will yield different outputs: - * - * - * echo $rijndael->encrypt(substr($plaintext, 0, 16)); - * echo $rijndael->encrypt(substr($plaintext, 16, 16)); - * - * - * echo $rijndael->encrypt($plaintext); - * - * - * The solution is to enable the continuous buffer. Although this will resolve the above discrepancy, it creates - * another, as demonstrated with the following: - * - * - * $rijndael->encrypt(substr($plaintext, 0, 16)); - * echo $rijndael->decrypt($des->encrypt(substr($plaintext, 16, 16))); - * - * - * echo $rijndael->decrypt($des->encrypt(substr($plaintext, 16, 16))); - * - * - * With the continuous buffer disabled, these would yield the same output. With it enabled, they yield different - * outputs. The reason is due to the fact that the initialization vector's change after every encryption / - * decryption round when the continuous buffer is enabled. When it's disabled, they remain constant. - * - * Put another way, when the continuous buffer is enabled, the state of the Crypt_Rijndael() object changes after each - * encryption / decryption round, whereas otherwise, it'd remain constant. For this reason, it's recommended that - * continuous buffers not be used. They do offer better security and are, in fact, sometimes required (SSH uses them), - * however, they are also less intuitive and more likely to cause you problems. - * - * @see Crypt_Rijndael::disableContinuousBuffer() - * @access public - */ - function enableContinuousBuffer() - { - $this->continuousBuffer = true; - } - - /** - * Treat consecutive packets as if they are a discontinuous buffer. - * - * The default behavior. - * - * @see Crypt_Rijndael::enableContinuousBuffer() - * @access public - */ - function disableContinuousBuffer() - { - $this->continuousBuffer = false; - $this->encryptIV = $this->iv; - $this->decryptIV = $this->iv; - } - - /** - * String Shift - * - * Inspired by array_shift - * - * @param String $string - * @param optional Integer $index - * @return String - * @access private - */ - function _string_shift(&$string, $index = 1) - { - $substr = substr($string, 0, $index); - $string = substr($string, $index); - return $substr; - } -} - -// vim: ts=4:sw=4:et: + + * setKey('abcdefghijklmnop'); + * + * $size = 10 * 1024; + * $plaintext = ''; + * for ($i = 0; $i < $size; $i++) { + * $plaintext.= 'a'; + * } + * + * echo $rijndael->decrypt($rijndael->encrypt($plaintext)); + * ?> + * + * + * LICENSE: This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * @category Crypt + * @package Crypt_Rijndael + * @author Jim Wigginton + * @copyright MMVIII Jim Wigginton + * @license http://www.gnu.org/licenses/lgpl.txt + * @version $Id: Rijndael.php,v 1.12 2010/02/09 06:10:26 terrafrost Exp $ + * @link http://phpseclib.sourceforge.net + */ + +/**#@+ + * @access public + * @see Crypt_Rijndael::encrypt() + * @see Crypt_Rijndael::decrypt() + */ +/** + * Encrypt / decrypt using the Counter mode. + * + * Set to -1 since that's what Crypt/Random.php uses to index the CTR mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Counter_.28CTR.29 + */ +define('CRYPT_RIJNDAEL_MODE_CTR', -1); +/** + * Encrypt / decrypt using the Electronic Code Book mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29 + */ +define('CRYPT_RIJNDAEL_MODE_ECB', 1); +/** + * Encrypt / decrypt using the Code Book Chaining mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29 + */ +define('CRYPT_RIJNDAEL_MODE_CBC', 2); +/**#@-*/ + +/**#@+ + * @access private + * @see Crypt_Rijndael::Crypt_Rijndael() + */ +/** + * Toggles the internal implementation + */ +define('CRYPT_RIJNDAEL_MODE_INTERNAL', 1); +/** + * Toggles the mcrypt implementation + */ +define('CRYPT_RIJNDAEL_MODE_MCRYPT', 2); +/**#@-*/ + +/** + * Pure-PHP implementation of Rijndael. + * + * @author Jim Wigginton + * @version 0.1.0 + * @access public + * @package Crypt_Rijndael + */ +class Crypt_Rijndael { + /** + * The Encryption Mode + * + * @see Crypt_Rijndael::Crypt_Rijndael() + * @var Integer + * @access private + */ + var $mode; + + /** + * The Key + * + * @see Crypt_Rijndael::setKey() + * @var String + * @access private + */ + var $key = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; + + /** + * The Initialization Vector + * + * @see Crypt_Rijndael::setIV() + * @var String + * @access private + */ + var $iv = ''; + + /** + * A "sliding" Initialization Vector + * + * @see Crypt_Rijndael::enableContinuousBuffer() + * @var String + * @access private + */ + var $encryptIV = ''; + + /** + * A "sliding" Initialization Vector + * + * @see Crypt_Rijndael::enableContinuousBuffer() + * @var String + * @access private + */ + var $decryptIV = ''; + + /** + * Continuous Buffer status + * + * @see Crypt_Rijndael::enableContinuousBuffer() + * @var Boolean + * @access private + */ + var $continuousBuffer = false; + + /** + * Padding status + * + * @see Crypt_Rijndael::enablePadding() + * @var Boolean + * @access private + */ + var $padding = true; + + /** + * Does the key schedule need to be (re)calculated? + * + * @see setKey() + * @see setBlockLength() + * @see setKeyLength() + * @var Boolean + * @access private + */ + var $changed = true; + + /** + * Has the key length explicitly been set or should it be derived from the key, itself? + * + * @see setKeyLength() + * @var Boolean + * @access private + */ + var $explicit_key_length = false; + + /** + * The Key Schedule + * + * @see _setup() + * @var Array + * @access private + */ + var $w; + + /** + * The Inverse Key Schedule + * + * @see _setup() + * @var Array + * @access private + */ + var $dw; + + /** + * The Block Length + * + * @see setBlockLength() + * @var Integer + * @access private + * @internal The max value is 32, the min value is 16. All valid values are multiples of 4. Exists in conjunction with + * $Nb because we need this value and not $Nb to pad strings appropriately. + */ + var $block_size = 16; + + /** + * The Block Length divided by 32 + * + * @see setBlockLength() + * @var Integer + * @access private + * @internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4. Exists in conjunction with $block_size + * because the encryption / decryption / key schedule creation requires this number and not $block_size. We could + * derive this from $block_size or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu + * of that, we'll just precompute it once. + * + */ + var $Nb = 4; + + /** + * The Key Length + * + * @see setKeyLength() + * @var Integer + * @access private + * @internal The max value is 256 / 8 = 32, the min value is 128 / 8 = 16. Exists in conjunction with $key_size + * because the encryption / decryption / key schedule creation requires this number and not $key_size. We could + * derive this from $key_size or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu + * of that, we'll just precompute it once. + */ + var $key_size = 16; + + /** + * The Key Length divided by 32 + * + * @see setKeyLength() + * @var Integer + * @access private + * @internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4 + */ + var $Nk = 4; + + /** + * The Number of Rounds + * + * @var Integer + * @access private + * @internal The max value is 14, the min value is 10. + */ + var $Nr; + + /** + * Shift offsets + * + * @var Array + * @access private + */ + var $c; + + /** + * Precomputed mixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $t0; + + /** + * Precomputed mixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $t1; + + /** + * Precomputed mixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $t2; + + /** + * Precomputed mixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $t3; + + /** + * Precomputed invMixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $dt0; + + /** + * Precomputed invMixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $dt1; + + /** + * Precomputed invMixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $dt2; + + /** + * Precomputed invMixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $dt3; + + /** + * Default Constructor. + * + * Determines whether or not the mcrypt extension should be used. $mode should only, at present, be + * CRYPT_RIJNDAEL_MODE_ECB or CRYPT_RIJNDAEL_MODE_CBC. If not explictly set, CRYPT_RIJNDAEL_MODE_CBC will be used. + * + * @param optional Integer $mode + * @return Crypt_Rijndael + * @access public + */ + function Crypt_Rijndael($mode = CRYPT_RIJNDAEL_MODE_CBC) + { + switch ($mode) { + case CRYPT_RIJNDAEL_MODE_ECB: + case CRYPT_RIJNDAEL_MODE_CBC: + case CRYPT_RIJNDAEL_MODE_CTR: + $this->mode = $mode; + break; + default: + $this->mode = CRYPT_RIJNDAEL_MODE_CBC; + } + + $t3 = &$this->t3; + $t2 = &$this->t2; + $t1 = &$this->t1; + $t0 = &$this->t0; + + $dt3 = &$this->dt3; + $dt2 = &$this->dt2; + $dt1 = &$this->dt1; + $dt0 = &$this->dt0; + + // according to (section 5.2.1), + // precomputed tables can be used in the mixColumns phase. in that example, they're assigned t0...t3, so + // those are the names we'll use. + $t3 = array( + 0x6363A5C6, 0x7C7C84F8, 0x777799EE, 0x7B7B8DF6, 0xF2F20DFF, 0x6B6BBDD6, 0x6F6FB1DE, 0xC5C55491, + 0x30305060, 0x01010302, 0x6767A9CE, 0x2B2B7D56, 0xFEFE19E7, 0xD7D762B5, 0xABABE64D, 0x76769AEC, + 0xCACA458F, 0x82829D1F, 0xC9C94089, 0x7D7D87FA, 0xFAFA15EF, 0x5959EBB2, 0x4747C98E, 0xF0F00BFB, + 0xADADEC41, 0xD4D467B3, 0xA2A2FD5F, 0xAFAFEA45, 0x9C9CBF23, 0xA4A4F753, 0x727296E4, 0xC0C05B9B, + 0xB7B7C275, 0xFDFD1CE1, 0x9393AE3D, 0x26266A4C, 0x36365A6C, 0x3F3F417E, 0xF7F702F5, 0xCCCC4F83, + 0x34345C68, 0xA5A5F451, 0xE5E534D1, 0xF1F108F9, 0x717193E2, 0xD8D873AB, 0x31315362, 0x15153F2A, + 0x04040C08, 0xC7C75295, 0x23236546, 0xC3C35E9D, 0x18182830, 0x9696A137, 0x05050F0A, 0x9A9AB52F, + 0x0707090E, 0x12123624, 0x80809B1B, 0xE2E23DDF, 0xEBEB26CD, 0x2727694E, 0xB2B2CD7F, 0x75759FEA, + 0x09091B12, 0x83839E1D, 0x2C2C7458, 0x1A1A2E34, 0x1B1B2D36, 0x6E6EB2DC, 0x5A5AEEB4, 0xA0A0FB5B, + 0x5252F6A4, 0x3B3B4D76, 0xD6D661B7, 0xB3B3CE7D, 0x29297B52, 0xE3E33EDD, 0x2F2F715E, 0x84849713, + 0x5353F5A6, 0xD1D168B9, 0x00000000, 0xEDED2CC1, 0x20206040, 0xFCFC1FE3, 0xB1B1C879, 0x5B5BEDB6, + 0x6A6ABED4, 0xCBCB468D, 0xBEBED967, 0x39394B72, 0x4A4ADE94, 0x4C4CD498, 0x5858E8B0, 0xCFCF4A85, + 0xD0D06BBB, 0xEFEF2AC5, 0xAAAAE54F, 0xFBFB16ED, 0x4343C586, 0x4D4DD79A, 0x33335566, 0x85859411, + 0x4545CF8A, 0xF9F910E9, 0x02020604, 0x7F7F81FE, 0x5050F0A0, 0x3C3C4478, 0x9F9FBA25, 0xA8A8E34B, + 0x5151F3A2, 0xA3A3FE5D, 0x4040C080, 0x8F8F8A05, 0x9292AD3F, 0x9D9DBC21, 0x38384870, 0xF5F504F1, + 0xBCBCDF63, 0xB6B6C177, 0xDADA75AF, 0x21216342, 0x10103020, 0xFFFF1AE5, 0xF3F30EFD, 0xD2D26DBF, + 0xCDCD4C81, 0x0C0C1418, 0x13133526, 0xECEC2FC3, 0x5F5FE1BE, 0x9797A235, 0x4444CC88, 0x1717392E, + 0xC4C45793, 0xA7A7F255, 0x7E7E82FC, 0x3D3D477A, 0x6464ACC8, 0x5D5DE7BA, 0x19192B32, 0x737395E6, + 0x6060A0C0, 0x81819819, 0x4F4FD19E, 0xDCDC7FA3, 0x22226644, 0x2A2A7E54, 0x9090AB3B, 0x8888830B, + 0x4646CA8C, 0xEEEE29C7, 0xB8B8D36B, 0x14143C28, 0xDEDE79A7, 0x5E5EE2BC, 0x0B0B1D16, 0xDBDB76AD, + 0xE0E03BDB, 0x32325664, 0x3A3A4E74, 0x0A0A1E14, 0x4949DB92, 0x06060A0C, 0x24246C48, 0x5C5CE4B8, + 0xC2C25D9F, 0xD3D36EBD, 0xACACEF43, 0x6262A6C4, 0x9191A839, 0x9595A431, 0xE4E437D3, 0x79798BF2, + 0xE7E732D5, 0xC8C8438B, 0x3737596E, 0x6D6DB7DA, 0x8D8D8C01, 0xD5D564B1, 0x4E4ED29C, 0xA9A9E049, + 0x6C6CB4D8, 0x5656FAAC, 0xF4F407F3, 0xEAEA25CF, 0x6565AFCA, 0x7A7A8EF4, 0xAEAEE947, 0x08081810, + 0xBABAD56F, 0x787888F0, 0x25256F4A, 0x2E2E725C, 0x1C1C2438, 0xA6A6F157, 0xB4B4C773, 0xC6C65197, + 0xE8E823CB, 0xDDDD7CA1, 0x74749CE8, 0x1F1F213E, 0x4B4BDD96, 0xBDBDDC61, 0x8B8B860D, 0x8A8A850F, + 0x707090E0, 0x3E3E427C, 0xB5B5C471, 0x6666AACC, 0x4848D890, 0x03030506, 0xF6F601F7, 0x0E0E121C, + 0x6161A3C2, 0x35355F6A, 0x5757F9AE, 0xB9B9D069, 0x86869117, 0xC1C15899, 0x1D1D273A, 0x9E9EB927, + 0xE1E138D9, 0xF8F813EB, 0x9898B32B, 0x11113322, 0x6969BBD2, 0xD9D970A9, 0x8E8E8907, 0x9494A733, + 0x9B9BB62D, 0x1E1E223C, 0x87879215, 0xE9E920C9, 0xCECE4987, 0x5555FFAA, 0x28287850, 0xDFDF7AA5, + 0x8C8C8F03, 0xA1A1F859, 0x89898009, 0x0D0D171A, 0xBFBFDA65, 0xE6E631D7, 0x4242C684, 0x6868B8D0, + 0x4141C382, 0x9999B029, 0x2D2D775A, 0x0F0F111E, 0xB0B0CB7B, 0x5454FCA8, 0xBBBBD66D, 0x16163A2C + ); + + $dt3 = array( + 0xF4A75051, 0x4165537E, 0x17A4C31A, 0x275E963A, 0xAB6BCB3B, 0x9D45F11F, 0xFA58ABAC, 0xE303934B, + 0x30FA5520, 0x766DF6AD, 0xCC769188, 0x024C25F5, 0xE5D7FC4F, 0x2ACBD7C5, 0x35448026, 0x62A38FB5, + 0xB15A49DE, 0xBA1B6725, 0xEA0E9845, 0xFEC0E15D, 0x2F7502C3, 0x4CF01281, 0x4697A38D, 0xD3F9C66B, + 0x8F5FE703, 0x929C9515, 0x6D7AEBBF, 0x5259DA95, 0xBE832DD4, 0x7421D358, 0xE0692949, 0xC9C8448E, + 0xC2896A75, 0x8E7978F4, 0x583E6B99, 0xB971DD27, 0xE14FB6BE, 0x88AD17F0, 0x20AC66C9, 0xCE3AB47D, + 0xDF4A1863, 0x1A3182E5, 0x51336097, 0x537F4562, 0x6477E0B1, 0x6BAE84BB, 0x81A01CFE, 0x082B94F9, + 0x48685870, 0x45FD198F, 0xDE6C8794, 0x7BF8B752, 0x73D323AB, 0x4B02E272, 0x1F8F57E3, 0x55AB2A66, + 0xEB2807B2, 0xB5C2032F, 0xC57B9A86, 0x3708A5D3, 0x2887F230, 0xBFA5B223, 0x036ABA02, 0x16825CED, + 0xCF1C2B8A, 0x79B492A7, 0x07F2F0F3, 0x69E2A14E, 0xDAF4CD65, 0x05BED506, 0x34621FD1, 0xA6FE8AC4, + 0x2E539D34, 0xF355A0A2, 0x8AE13205, 0xF6EB75A4, 0x83EC390B, 0x60EFAA40, 0x719F065E, 0x6E1051BD, + 0x218AF93E, 0xDD063D96, 0x3E05AEDD, 0xE6BD464D, 0x548DB591, 0xC45D0571, 0x06D46F04, 0x5015FF60, + 0x98FB2419, 0xBDE997D6, 0x4043CC89, 0xD99E7767, 0xE842BDB0, 0x898B8807, 0x195B38E7, 0xC8EEDB79, + 0x7C0A47A1, 0x420FE97C, 0x841EC9F8, 0x00000000, 0x80868309, 0x2BED4832, 0x1170AC1E, 0x5A724E6C, + 0x0EFFFBFD, 0x8538560F, 0xAED51E3D, 0x2D392736, 0x0FD9640A, 0x5CA62168, 0x5B54D19B, 0x362E3A24, + 0x0A67B10C, 0x57E70F93, 0xEE96D2B4, 0x9B919E1B, 0xC0C54F80, 0xDC20A261, 0x774B695A, 0x121A161C, + 0x93BA0AE2, 0xA02AE5C0, 0x22E0433C, 0x1B171D12, 0x090D0B0E, 0x8BC7ADF2, 0xB6A8B92D, 0x1EA9C814, + 0xF1198557, 0x75074CAF, 0x99DDBBEE, 0x7F60FDA3, 0x01269FF7, 0x72F5BC5C, 0x663BC544, 0xFB7E345B, + 0x4329768B, 0x23C6DCCB, 0xEDFC68B6, 0xE4F163B8, 0x31DCCAD7, 0x63851042, 0x97224013, 0xC6112084, + 0x4A247D85, 0xBB3DF8D2, 0xF93211AE, 0x29A16DC7, 0x9E2F4B1D, 0xB230F3DC, 0x8652EC0D, 0xC1E3D077, + 0xB3166C2B, 0x70B999A9, 0x9448FA11, 0xE9642247, 0xFC8CC4A8, 0xF03F1AA0, 0x7D2CD856, 0x3390EF22, + 0x494EC787, 0x38D1C1D9, 0xCAA2FE8C, 0xD40B3698, 0xF581CFA6, 0x7ADE28A5, 0xB78E26DA, 0xADBFA43F, + 0x3A9DE42C, 0x78920D50, 0x5FCC9B6A, 0x7E466254, 0x8D13C2F6, 0xD8B8E890, 0x39F75E2E, 0xC3AFF582, + 0x5D80BE9F, 0xD0937C69, 0xD52DA96F, 0x2512B3CF, 0xAC993BC8, 0x187DA710, 0x9C636EE8, 0x3BBB7BDB, + 0x267809CD, 0x5918F46E, 0x9AB701EC, 0x4F9AA883, 0x956E65E6, 0xFFE67EAA, 0xBCCF0821, 0x15E8E6EF, + 0xE79BD9BA, 0x6F36CE4A, 0x9F09D4EA, 0xB07CD629, 0xA4B2AF31, 0x3F23312A, 0xA59430C6, 0xA266C035, + 0x4EBC3774, 0x82CAA6FC, 0x90D0B0E0, 0xA7D81533, 0x04984AF1, 0xECDAF741, 0xCD500E7F, 0x91F62F17, + 0x4DD68D76, 0xEFB04D43, 0xAA4D54CC, 0x9604DFE4, 0xD1B5E39E, 0x6A881B4C, 0x2C1FB8C1, 0x65517F46, + 0x5EEA049D, 0x8C355D01, 0x877473FA, 0x0B412EFB, 0x671D5AB3, 0xDBD25292, 0x105633E9, 0xD647136D, + 0xD7618C9A, 0xA10C7A37, 0xF8148E59, 0x133C89EB, 0xA927EECE, 0x61C935B7, 0x1CE5EDE1, 0x47B13C7A, + 0xD2DF599C, 0xF2733F55, 0x14CE7918, 0xC737BF73, 0xF7CDEA53, 0xFDAA5B5F, 0x3D6F14DF, 0x44DB8678, + 0xAFF381CA, 0x68C43EB9, 0x24342C38, 0xA3405FC2, 0x1DC37216, 0xE2250CBC, 0x3C498B28, 0x0D9541FF, + 0xA8017139, 0x0CB3DE08, 0xB4E49CD8, 0x56C19064, 0xCB84617B, 0x32B670D5, 0x6C5C7448, 0xB85742D0 + ); + + for ($i = 0; $i < 256; $i++) { + $t2[$i << 8] = (($t3[$i] << 8) & 0xFFFFFF00) | (($t3[$i] >> 24) & 0x000000FF); + $t1[$i << 16] = (($t3[$i] << 16) & 0xFFFF0000) | (($t3[$i] >> 16) & 0x0000FFFF); + $t0[$i << 24] = (($t3[$i] << 24) & 0xFF000000) | (($t3[$i] >> 8) & 0x00FFFFFF); + + $dt2[$i << 8] = (($this->dt3[$i] << 8) & 0xFFFFFF00) | (($dt3[$i] >> 24) & 0x000000FF); + $dt1[$i << 16] = (($this->dt3[$i] << 16) & 0xFFFF0000) | (($dt3[$i] >> 16) & 0x0000FFFF); + $dt0[$i << 24] = (($this->dt3[$i] << 24) & 0xFF000000) | (($dt3[$i] >> 8) & 0x00FFFFFF); + } + } + + /** + * Sets the key. + * + * Keys can be of any length. Rijndael, itself, requires the use of a key that's between 128-bits and 256-bits long and + * whose length is a multiple of 32. If the key is less than 256-bits and the key length isn't set, we round the length + * up to the closest valid key length, padding $key with null bytes. If the key is more than 256-bits, we trim the + * excess bits. + * + * If the key is not explicitly set, it'll be assumed to be all null bytes. + * + * @access public + * @param String $key + */ + function setKey($key) + { + $this->key = $key; + $this->changed = true; + } + + /** + * Sets the initialization vector. (optional) + * + * SetIV is not required when CRYPT_RIJNDAEL_MODE_ECB is being used. If not explictly set, it'll be assumed + * to be all zero's. + * + * @access public + * @param String $iv + */ + function setIV($iv) + { + $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($iv, 0, $this->block_size), $this->block_size, chr(0));; + } + + /** + * Sets the key length + * + * Valid key lengths are 128, 160, 192, 224, and 256. If the length is less than 128, it will be rounded up to + * 128. If the length is greater then 128 and invalid, it will be rounded down to the closest valid amount. + * + * @access public + * @param Integer $length + */ + function setKeyLength($length) + { + $length >>= 5; + if ($length > 8) { + $length = 8; + } else if ($length < 4) { + $length = 4; + } + $this->Nk = $length; + $this->key_size = $length << 2; + + $this->explicit_key_length = true; + $this->changed = true; + } + + /** + * Sets the block length + * + * Valid block lengths are 128, 160, 192, 224, and 256. If the length is less than 128, it will be rounded up to + * 128. If the length is greater then 128 and invalid, it will be rounded down to the closest valid amount. + * + * @access public + * @param Integer $length + */ + function setBlockLength($length) + { + $length >>= 5; + if ($length > 8) { + $length = 8; + } else if ($length < 4) { + $length = 4; + } + $this->Nb = $length; + $this->block_size = $length << 2; + $this->changed = true; + } + + /** + * Generate CTR XOR encryption key + * + * Encrypt the output of this and XOR it against the ciphertext / plaintext to get the + * plaintext / ciphertext in CTR mode. + * + * @see Crypt_Rijndael::decrypt() + * @see Crypt_Rijndael::encrypt() + * @access public + * @param Integer $length + * @param String $iv + */ + function _generate_xor($length, &$iv) + { + $xor = ''; + $block_size = $this->block_size; + $num_blocks = floor(($length + ($block_size - 1)) / $block_size); + for ($i = 0; $i < $num_blocks; $i++) { + $xor.= $iv; + for ($j = 4; $j <= $block_size; $j+=4) { + $temp = substr($iv, -$j, 4); + switch ($temp) { + case "\xFF\xFF\xFF\xFF": + $iv = substr_replace($iv, "\x00\x00\x00\x00", -$j, 4); + break; + case "\x7F\xFF\xFF\xFF": + $iv = substr_replace($iv, "\x80\x00\x00\x00", -$j, 4); + break 2; + default: + extract(unpack('Ncount', $temp)); + $iv = substr_replace($iv, pack('N', $count + 1), -$j, 4); + break 2; + } + } + } + + return $xor; + } + + /** + * Encrypts a message. + * + * $plaintext will be padded with additional bytes such that it's length is a multiple of the block size. Other Rjindael + * implementations may or may not pad in the same manner. Other common approaches to padding and the reasons why it's + * necessary are discussed in the following + * URL: + * + * {@link http://www.di-mgt.com.au/cryptopad.html http://www.di-mgt.com.au/cryptopad.html} + * + * An alternative to padding is to, separately, send the length of the file. This is what SSH, in fact, does. + * strlen($plaintext) will still need to be a multiple of 8, however, arbitrary values can be added to make it that + * length. + * + * @see Crypt_Rijndael::decrypt() + * @access public + * @param String $plaintext + */ + function encrypt($plaintext) + { + $this->_setup(); + if ($this->mode != CRYPT_RIJNDAEL_MODE_CTR) { + $plaintext = $this->_pad($plaintext); + } + + $block_size = $this->block_size; + $ciphertext = ''; + switch ($this->mode) { + case CRYPT_RIJNDAEL_MODE_ECB: + for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { + $ciphertext.= $this->_encryptBlock(substr($plaintext, $i, $block_size)); + } + break; + case CRYPT_RIJNDAEL_MODE_CBC: + $xor = $this->encryptIV; + for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { + $block = substr($plaintext, $i, $block_size); + $block = $this->_encryptBlock($block ^ $xor); + $xor = $block; + $ciphertext.= $block; + } + if ($this->continuousBuffer) { + $this->encryptIV = $xor; + } + break; + case CRYPT_RIJNDAEL_MODE_CTR: + $xor = $this->encryptIV; + for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { + $block = substr($plaintext, $i, $block_size); + $key = $this->_encryptBlock($this->_generate_xor($block_size, $xor)); + $ciphertext.= $block ^ $key; + } + if ($this->continuousBuffer) { + $this->encryptIV = $xor; + } + } + + return $ciphertext; + } + + /** + * Decrypts a message. + * + * If strlen($ciphertext) is not a multiple of the block size, null bytes will be added to the end of the string until + * it is. + * + * @see Crypt_Rijndael::encrypt() + * @access public + * @param String $ciphertext + */ + function decrypt($ciphertext) + { + $this->_setup(); + + if ($this->mode != CRYPT_RIJNDAEL_MODE_CTR) { + // we pad with chr(0) since that's what mcrypt_generic does. to quote from http://php.net/function.mcrypt-generic : + // "The data is padded with "\0" to make sure the length of the data is n * blocksize." + $ciphertext = str_pad($ciphertext, (strlen($ciphertext) + $this->block_size - 1) % $this->block_size, chr(0)); + } + + $block_size = $this->block_size; + $plaintext = ''; + switch ($this->mode) { + case CRYPT_RIJNDAEL_MODE_ECB: + for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { + $plaintext.= $this->_decryptBlock(substr($ciphertext, $i, $block_size)); + } + break; + case CRYPT_RIJNDAEL_MODE_CBC: + $xor = $this->decryptIV; + for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { + $block = substr($ciphertext, $i, $block_size); + $plaintext.= $this->_decryptBlock($block) ^ $xor; + $xor = $block; + } + if ($this->continuousBuffer) { + $this->decryptIV = $xor; + } + break; + case CRYPT_RIJNDAEL_MODE_CTR: + $xor = $this->decryptIV; + for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { + $block = substr($ciphertext, $i, $block_size); + $key = $this->_encryptBlock($this->_generate_xor($block_size, $xor)); + $plaintext.= $block ^ $key; + } + if ($this->continuousBuffer) { + $this->decryptIV = $xor; + } + } + + return $this->mode != CRYPT_RIJNDAEL_MODE_CTR ? $this->_unpad($plaintext) : $plaintext; + } + + /** + * Encrypts a block + * + * @access private + * @param String $in + * @return String + */ + function _encryptBlock($in) + { + $state = array(); + $words = unpack('N*word', $in); + + $w = $this->w; + $t0 = $this->t0; + $t1 = $this->t1; + $t2 = $this->t2; + $t3 = $this->t3; + $Nb = $this->Nb; + $Nr = $this->Nr; + $c = $this->c; + + // addRoundKey + $i = 0; + foreach ($words as $word) { + $state[] = $word ^ $w[0][$i++]; + } + + // fips-197.pdf#page=19, "Figure 5. Pseudo Code for the Cipher", states that this loop has four components - + // subBytes, shiftRows, mixColumns, and addRoundKey. fips-197.pdf#page=30, "Implementation Suggestions Regarding + // Various Platforms" suggests that performs enhanced implementations are described in Rijndael-ammended.pdf. + // Rijndael-ammended.pdf#page=20, "Implementation aspects / 32-bit processor", discusses such an optimization. + // Unfortunately, the description given there is not quite correct. Per aes.spec.v316.pdf#page=19 [1], + // equation (7.4.7) is supposed to use addition instead of subtraction, so we'll do that here, as well. + + // [1] http://fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.v316.pdf + $temp = array(); + for ($round = 1; $round < $Nr; $round++) { + $i = 0; // $c[0] == 0 + $j = $c[1]; + $k = $c[2]; + $l = $c[3]; + + while ($i < $this->Nb) { + $temp[$i] = $t0[$state[$i] & 0xFF000000] ^ + $t1[$state[$j] & 0x00FF0000] ^ + $t2[$state[$k] & 0x0000FF00] ^ + $t3[$state[$l] & 0x000000FF] ^ + $w[$round][$i]; + $i++; + $j = ($j + 1) % $Nb; + $k = ($k + 1) % $Nb; + $l = ($l + 1) % $Nb; + } + + for ($i = 0; $i < $Nb; $i++) { + $state[$i] = $temp[$i]; + } + } + + // subWord + for ($i = 0; $i < $Nb; $i++) { + $state[$i] = $this->_subWord($state[$i]); + } + + // shiftRows + addRoundKey + $i = 0; // $c[0] == 0 + $j = $c[1]; + $k = $c[2]; + $l = $c[3]; + while ($i < $this->Nb) { + $temp[$i] = ($state[$i] & 0xFF000000) ^ + ($state[$j] & 0x00FF0000) ^ + ($state[$k] & 0x0000FF00) ^ + ($state[$l] & 0x000000FF) ^ + $w[$Nr][$i]; + $i++; + $j = ($j + 1) % $Nb; + $k = ($k + 1) % $Nb; + $l = ($l + 1) % $Nb; + } + $state = $temp; + + array_unshift($state, 'N*'); + + return call_user_func_array('pack', $state); + } + + /** + * Decrypts a block + * + * @access private + * @param String $in + * @return String + */ + function _decryptBlock($in) + { + $state = array(); + $words = unpack('N*word', $in); + + $num_states = count($state); + $dw = $this->dw; + $dt0 = $this->dt0; + $dt1 = $this->dt1; + $dt2 = $this->dt2; + $dt3 = $this->dt3; + $Nb = $this->Nb; + $Nr = $this->Nr; + $c = $this->c; + + // addRoundKey + $i = 0; + foreach ($words as $word) { + $state[] = $word ^ $dw[$Nr][$i++]; + } + + $temp = array(); + for ($round = $Nr - 1; $round > 0; $round--) { + $i = 0; // $c[0] == 0 + $j = $Nb - $c[1]; + $k = $Nb - $c[2]; + $l = $Nb - $c[3]; + + while ($i < $Nb) { + $temp[$i] = $dt0[$state[$i] & 0xFF000000] ^ + $dt1[$state[$j] & 0x00FF0000] ^ + $dt2[$state[$k] & 0x0000FF00] ^ + $dt3[$state[$l] & 0x000000FF] ^ + $dw[$round][$i]; + $i++; + $j = ($j + 1) % $Nb; + $k = ($k + 1) % $Nb; + $l = ($l + 1) % $Nb; + } + + for ($i = 0; $i < $Nb; $i++) { + $state[$i] = $temp[$i]; + } + } + + // invShiftRows + invSubWord + addRoundKey + $i = 0; // $c[0] == 0 + $j = $Nb - $c[1]; + $k = $Nb - $c[2]; + $l = $Nb - $c[3]; + + while ($i < $Nb) { + $temp[$i] = $dw[0][$i] ^ + $this->_invSubWord(($state[$i] & 0xFF000000) | + ($state[$j] & 0x00FF0000) | + ($state[$k] & 0x0000FF00) | + ($state[$l] & 0x000000FF)); + $i++; + $j = ($j + 1) % $Nb; + $k = ($k + 1) % $Nb; + $l = ($l + 1) % $Nb; + } + + $state = $temp; + + array_unshift($state, 'N*'); + + return call_user_func_array('pack', $state); + } + + /** + * Setup Rijndael + * + * Validates all the variables and calculates $Nr - the number of rounds that need to be performed - and $w - the key + * key schedule. + * + * @access private + */ + function _setup() + { + // Each number in $rcon is equal to the previous number multiplied by two in Rijndael's finite field. + // See http://en.wikipedia.org/wiki/Finite_field_arithmetic#Multiplicative_inverse + static $rcon = array(0, + 0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, + 0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000, + 0x6C000000, 0xD8000000, 0xAB000000, 0x4D000000, 0x9A000000, + 0x2F000000, 0x5E000000, 0xBC000000, 0x63000000, 0xC6000000, + 0x97000000, 0x35000000, 0x6A000000, 0xD4000000, 0xB3000000, + 0x7D000000, 0xFA000000, 0xEF000000, 0xC5000000, 0x91000000 + ); + + if (!$this->changed) { + return; + } + + if (!$this->explicit_key_length) { + // we do >> 2, here, and not >> 5, as we do above, since strlen($this->key) tells us the number of bytes - not bits + $length = strlen($this->key) >> 2; + if ($length > 8) { + $length = 8; + } else if ($length < 4) { + $length = 4; + } + $this->Nk = $length; + $this->key_size = $length << 2; + } + + $this->key = str_pad(substr($this->key, 0, $this->key_size), $this->key_size, chr(0)); + $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($this->iv, 0, $this->block_size), $this->block_size, chr(0)); + + // see Rijndael-ammended.pdf#page=44 + $this->Nr = max($this->Nk, $this->Nb) + 6; + + // shift offsets for Nb = 5, 7 are defined in Rijndael-ammended.pdf#page=44, + // "Table 8: Shift offsets in Shiftrow for the alternative block lengths" + // shift offsets for Nb = 4, 6, 8 are defined in Rijndael-ammended.pdf#page=14, + // "Table 2: Shift offsets for different block lengths" + switch ($this->Nb) { + case 4: + case 5: + case 6: + $this->c = array(0, 1, 2, 3); + break; + case 7: + $this->c = array(0, 1, 2, 4); + break; + case 8: + $this->c = array(0, 1, 3, 4); + } + + $key = $this->key; + + $w = array_values(unpack('N*words', $key)); + + $length = $this->Nb * ($this->Nr + 1); + for ($i = $this->Nk; $i < $length; $i++) { + $temp = $w[$i - 1]; + if ($i % $this->Nk == 0) { + // according to , "the size of an integer is platform-dependent". + // on a 32-bit machine, it's 32-bits, and on a 64-bit machine, it's 64-bits. on a 32-bit machine, + // 0xFFFFFFFF << 8 == 0xFFFFFF00, but on a 64-bit machine, it equals 0xFFFFFFFF00. as such, doing 'and' + // with 0xFFFFFFFF (or 0xFFFFFF00) on a 32-bit machine is unnecessary, but on a 64-bit machine, it is. + $temp = (($temp << 8) & 0xFFFFFF00) | (($temp >> 24) & 0x000000FF); // rotWord + $temp = $this->_subWord($temp) ^ $rcon[$i / $this->Nk]; + } else if ($this->Nk > 6 && $i % $this->Nk == 4) { + $temp = $this->_subWord($temp); + } + $w[$i] = $w[$i - $this->Nk] ^ $temp; + } + + // convert the key schedule from a vector of $Nb * ($Nr + 1) length to a matrix with $Nr + 1 rows and $Nb columns + // and generate the inverse key schedule. more specifically, + // according to (section 5.3.3), + // "The key expansion for the Inverse Cipher is defined as follows: + // 1. Apply the Key Expansion. + // 2. Apply InvMixColumn to all Round Keys except the first and the last one." + // also, see fips-197.pdf#page=27, "5.3.5 Equivalent Inverse Cipher" + $temp = array(); + for ($i = $row = $col = 0; $i < $length; $i++, $col++) { + if ($col == $this->Nb) { + if ($row == 0) { + $this->dw[0] = $this->w[0]; + } else { + // subWord + invMixColumn + invSubWord = invMixColumn + $j = 0; + while ($j < $this->Nb) { + $dw = $this->_subWord($this->w[$row][$j]); + $temp[$j] = $this->dt0[$dw & 0xFF000000] ^ + $this->dt1[$dw & 0x00FF0000] ^ + $this->dt2[$dw & 0x0000FF00] ^ + $this->dt3[$dw & 0x000000FF]; + $j++; + } + $this->dw[$row] = $temp; + } + + $col = 0; + $row++; + } + $this->w[$row][$col] = $w[$i]; + } + + $this->dw[$row] = $this->w[$row]; + + $this->changed = false; + } + + /** + * Performs S-Box substitutions + * + * @access private + */ + function _subWord($word) + { + static $sbox0, $sbox1, $sbox2, $sbox3; + + if (empty($sbox0)) { + $sbox0 = array( + 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, + 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, + 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, + 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, + 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, + 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, + 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, + 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, + 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, + 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, + 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, + 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, + 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, + 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, + 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, + 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16 + ); + + $sbox1 = array(); + $sbox2 = array(); + $sbox3 = array(); + + for ($i = 0; $i < 256; $i++) { + $sbox1[$i << 8] = $sbox0[$i] << 8; + $sbox2[$i << 16] = $sbox0[$i] << 16; + $sbox3[$i << 24] = $sbox0[$i] << 24; + } + } + + return $sbox0[$word & 0x000000FF] | + $sbox1[$word & 0x0000FF00] | + $sbox2[$word & 0x00FF0000] | + $sbox3[$word & 0xFF000000]; + } + + /** + * Performs inverse S-Box substitutions + * + * @access private + */ + function _invSubWord($word) + { + static $sbox0, $sbox1, $sbox2, $sbox3; + + if (empty($sbox0)) { + $sbox0 = array( + 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, + 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, + 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, + 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, + 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, + 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, + 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06, + 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, + 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, + 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, + 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, + 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, + 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, + 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, + 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D + ); + + $sbox1 = array(); + $sbox2 = array(); + $sbox3 = array(); + + for ($i = 0; $i < 256; $i++) { + $sbox1[$i << 8] = $sbox0[$i] << 8; + $sbox2[$i << 16] = $sbox0[$i] << 16; + $sbox3[$i << 24] = $sbox0[$i] << 24; + } + } + + return $sbox0[$word & 0x000000FF] | + $sbox1[$word & 0x0000FF00] | + $sbox2[$word & 0x00FF0000] | + $sbox3[$word & 0xFF000000]; + } + + /** + * Pad "packets". + * + * Rijndael works by encrypting between sixteen and thirty-two bytes at a time, provided that number is also a multiple + * of four. If you ever need to encrypt or decrypt something that isn't of the proper length, it becomes necessary to + * pad the input so that it is of the proper length. + * + * Padding is enabled by default. Sometimes, however, it is undesirable to pad strings. Such is the case in SSH, + * where "packets" are padded with random bytes before being encrypted. Unpad these packets and you risk stripping + * away characters that shouldn't be stripped away. (SSH knows how many bytes are added because the length is + * transmitted separately) + * + * @see Crypt_Rijndael::disablePadding() + * @access public + */ + function enablePadding() + { + $this->padding = true; + } + + /** + * Do not pad packets. + * + * @see Crypt_Rijndael::enablePadding() + * @access public + */ + function disablePadding() + { + $this->padding = false; + } + + /** + * Pads a string + * + * Pads a string using the RSA PKCS padding standards so that its length is a multiple of the blocksize. + * $block_size - (strlen($text) % $block_size) bytes are added, each of which is equal to + * chr($block_size - (strlen($text) % $block_size) + * + * If padding is disabled and $text is not a multiple of the blocksize, the string will be padded regardless + * and padding will, hence forth, be enabled. + * + * @see Crypt_Rijndael::_unpad() + * @access private + */ + function _pad($text) + { + $length = strlen($text); + + if (!$this->padding) { + if ($length % $this->block_size == 0) { + return $text; + } else { + user_error("The plaintext's length ($length) is not a multiple of the block size ({$this->block_size})", E_USER_NOTICE); + $this->padding = true; + } + } + + $pad = $this->block_size - ($length % $this->block_size); + + return str_pad($text, $length + $pad, chr($pad)); + } + + /** + * Unpads a string. + * + * If padding is enabled and the reported padding length is invalid the encryption key will be assumed to be wrong + * and false will be returned. + * + * @see Crypt_Rijndael::_pad() + * @access private + */ + function _unpad($text) + { + if (!$this->padding) { + return $text; + } + + $length = ord($text[strlen($text) - 1]); + + if (!$length || $length > $this->block_size) { + return false; + } + + return substr($text, 0, -$length); + } + + /** + * Treat consecutive "packets" as if they are a continuous buffer. + * + * Say you have a 32-byte plaintext $plaintext. Using the default behavior, the two following code snippets + * will yield different outputs: + * + * + * echo $rijndael->encrypt(substr($plaintext, 0, 16)); + * echo $rijndael->encrypt(substr($plaintext, 16, 16)); + * + * + * echo $rijndael->encrypt($plaintext); + * + * + * The solution is to enable the continuous buffer. Although this will resolve the above discrepancy, it creates + * another, as demonstrated with the following: + * + * + * $rijndael->encrypt(substr($plaintext, 0, 16)); + * echo $rijndael->decrypt($des->encrypt(substr($plaintext, 16, 16))); + * + * + * echo $rijndael->decrypt($des->encrypt(substr($plaintext, 16, 16))); + * + * + * With the continuous buffer disabled, these would yield the same output. With it enabled, they yield different + * outputs. The reason is due to the fact that the initialization vector's change after every encryption / + * decryption round when the continuous buffer is enabled. When it's disabled, they remain constant. + * + * Put another way, when the continuous buffer is enabled, the state of the Crypt_Rijndael() object changes after each + * encryption / decryption round, whereas otherwise, it'd remain constant. For this reason, it's recommended that + * continuous buffers not be used. They do offer better security and are, in fact, sometimes required (SSH uses them), + * however, they are also less intuitive and more likely to cause you problems. + * + * @see Crypt_Rijndael::disableContinuousBuffer() + * @access public + */ + function enableContinuousBuffer() + { + $this->continuousBuffer = true; + } + + /** + * Treat consecutive packets as if they are a discontinuous buffer. + * + * The default behavior. + * + * @see Crypt_Rijndael::enableContinuousBuffer() + * @access public + */ + function disableContinuousBuffer() + { + $this->continuousBuffer = false; + $this->encryptIV = $this->iv; + $this->decryptIV = $this->iv; + } + + /** + * String Shift + * + * Inspired by array_shift + * + * @param String $string + * @param optional Integer $index + * @return String + * @access private + */ + function _string_shift(&$string, $index = 1) + { + $substr = substr($string, 0, $index); + $string = substr($string, $index); + return $substr; + } +} + +// vim: ts=4:sw=4:et: // vim6: fdl=1: \ No newline at end of file diff --git a/plugins/OStatus/extlib/Crypt/TripleDES.php b/plugins/OStatus/extlib/Crypt/TripleDES.php index 03050e5d6..9d054086a 100644 --- a/plugins/OStatus/extlib/Crypt/TripleDES.php +++ b/plugins/OStatus/extlib/Crypt/TripleDES.php @@ -1,603 +1,690 @@ - - * setKey('abcdefghijklmnopqrstuvwx'); - * - * $size = 10 * 1024; - * $plaintext = ''; - * for ($i = 0; $i < $size; $i++) { - * $plaintext.= 'a'; - * } - * - * echo $des->decrypt($des->encrypt($plaintext)); - * ?> - * - * - * LICENSE: This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, - * MA 02111-1307 USA - * - * @category Crypt - * @package Crypt_TripleDES - * @author Jim Wigginton - * @copyright MMVII Jim Wigginton - * @license http://www.gnu.org/licenses/lgpl.txt - * @version $Id: TripleDES.php,v 1.9 2009/11/23 19:06:07 terrafrost Exp $ - * @link http://phpseclib.sourceforge.net - */ - -/** - * Include Crypt_DES - */ -require_once 'DES.php'; - -/** - * Encrypt / decrypt using inner chaining - * - * Inner chaining is used by SSH-1 and is generally considered to be less secure then outer chaining (CRYPT_DES_MODE_CBC3). - */ -define('CRYPT_DES_MODE_3CBC', 3); - -/** - * Encrypt / decrypt using outer chaining - * - * Outer chaining is used by SSH-2 and when the mode is set to CRYPT_DES_MODE_CBC. - */ -define('CRYPT_DES_MODE_CBC3', CRYPT_DES_MODE_CBC); - -/** - * Pure-PHP implementation of Triple DES. - * - * @author Jim Wigginton - * @version 0.1.0 - * @access public - * @package Crypt_TerraDES - */ -class Crypt_TripleDES { - /** - * The Three Keys - * - * @see Crypt_TripleDES::setKey() - * @var String - * @access private - */ - var $key = "\0\0\0\0\0\0\0\0"; - - /** - * The Encryption Mode - * - * @see Crypt_TripleDES::Crypt_TripleDES() - * @var Integer - * @access private - */ - var $mode = CRYPT_DES_MODE_CBC; - - /** - * Continuous Buffer status - * - * @see Crypt_TripleDES::enableContinuousBuffer() - * @var Boolean - * @access private - */ - var $continuousBuffer = false; - - /** - * Padding status - * - * @see Crypt_TripleDES::enablePadding() - * @var Boolean - * @access private - */ - var $padding = true; - - /** - * The Initialization Vector - * - * @see Crypt_TripleDES::setIV() - * @var String - * @access private - */ - var $iv = "\0\0\0\0\0\0\0\0"; - - /** - * A "sliding" Initialization Vector - * - * @see Crypt_TripleDES::enableContinuousBuffer() - * @var String - * @access private - */ - var $encryptIV = "\0\0\0\0\0\0\0\0"; - - /** - * A "sliding" Initialization Vector - * - * @see Crypt_TripleDES::enableContinuousBuffer() - * @var String - * @access private - */ - var $decryptIV = "\0\0\0\0\0\0\0\0"; - - /** - * MCrypt parameters - * - * @see Crypt_TripleDES::setMCrypt() - * @var Array - * @access private - */ - var $mcrypt = array('', ''); - - /** - * The Crypt_DES objects - * - * @var Array - * @access private - */ - var $des; - - /** - * Default Constructor. - * - * Determines whether or not the mcrypt extension should be used. $mode should only, at present, be - * CRYPT_DES_MODE_ECB or CRYPT_DES_MODE_CBC. If not explictly set, CRYPT_DES_MODE_CBC will be used. - * - * @param optional Integer $mode - * @return Crypt_TripleDES - * @access public - */ - function Crypt_TripleDES($mode = CRYPT_DES_MODE_CBC) - { - if ( !defined('CRYPT_DES_MODE') ) { - switch (true) { - case extension_loaded('mcrypt'): - // i'd check to see if des was supported, by doing in_array('des', mcrypt_list_algorithms('')), - // but since that can be changed after the object has been created, there doesn't seem to be - // a lot of point... - define('CRYPT_DES_MODE', CRYPT_DES_MODE_MCRYPT); - break; - default: - define('CRYPT_DES_MODE', CRYPT_DES_MODE_INTERNAL); - } - } - - if ( $mode == CRYPT_DES_MODE_3CBC ) { - $this->mode = CRYPT_DES_MODE_3CBC; - $this->des = array( - new Crypt_DES(CRYPT_DES_MODE_CBC), - new Crypt_DES(CRYPT_DES_MODE_CBC), - new Crypt_DES(CRYPT_DES_MODE_CBC) - ); - - // we're going to be doing the padding, ourselves, so disable it in the Crypt_DES objects - $this->des[0]->disablePadding(); - $this->des[1]->disablePadding(); - $this->des[2]->disablePadding(); - - return; - } - - switch ( CRYPT_DES_MODE ) { - case CRYPT_DES_MODE_MCRYPT: - switch ($mode) { - case CRYPT_DES_MODE_ECB: - $this->mode = MCRYPT_MODE_ECB; break; - case CRYPT_DES_MODE_CBC: - default: - $this->mode = MCRYPT_MODE_CBC; - } - - break; - default: - $this->des = array( - new Crypt_DES(CRYPT_DES_MODE_ECB), - new Crypt_DES(CRYPT_DES_MODE_ECB), - new Crypt_DES(CRYPT_DES_MODE_ECB) - ); - - // we're going to be doing the padding, ourselves, so disable it in the Crypt_DES objects - $this->des[0]->disablePadding(); - $this->des[1]->disablePadding(); - $this->des[2]->disablePadding(); - - switch ($mode) { - case CRYPT_DES_MODE_ECB: - case CRYPT_DES_MODE_CBC: - $this->mode = $mode; - break; - default: - $this->mode = CRYPT_DES_MODE_CBC; - } - } - } - - /** - * Sets the key. - * - * Keys can be of any length. Triple DES, itself, can use 128-bit (eg. strlen($key) == 16) or - * 192-bit (eg. strlen($key) == 24) keys. This function pads and truncates $key as appropriate. - * - * DES also requires that every eighth bit be a parity bit, however, we'll ignore that. - * - * If the key is not explicitly set, it'll be assumed to be all zero's. - * - * @access public - * @param String $key - */ - function setKey($key) - { - $length = strlen($key); - if ($length > 8) { - $key = str_pad($key, 24, chr(0)); - // if $key is between 64 and 128-bits, use the first 64-bits as the last, per this: - // http://php.net/function.mcrypt-encrypt#47973 - $key = $length <= 16 ? substr_replace($key, substr($key, 0, 8), 16) : substr($key, 0, 24); - } - $this->key = $key; - switch (true) { - case CRYPT_DES_MODE == CRYPT_DES_MODE_INTERNAL: - case $this->mode == CRYPT_DES_MODE_3CBC: - $this->des[0]->setKey(substr($key, 0, 8)); - $this->des[1]->setKey(substr($key, 8, 8)); - $this->des[2]->setKey(substr($key, 16, 8)); - } - } - - /** - * Sets the initialization vector. (optional) - * - * SetIV is not required when CRYPT_DES_MODE_ECB is being used. If not explictly set, it'll be assumed - * to be all zero's. - * - * @access public - * @param String $iv - */ - function setIV($iv) - { - $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($iv, 0, 8), 8, chr(0)); - if ($this->mode == CRYPT_DES_MODE_3CBC) { - $this->des[0]->setIV($iv); - $this->des[1]->setIV($iv); - $this->des[2]->setIV($iv); - } - } - - /** - * Sets MCrypt parameters. (optional) - * - * If MCrypt is being used, empty strings will be used, unless otherwise specified. - * - * @link http://php.net/function.mcrypt-module-open#function.mcrypt-module-open - * @access public - * @param optional Integer $algorithm_directory - * @param optional Integer $mode_directory - */ - function setMCrypt($algorithm_directory = '', $mode_directory = '') - { - $this->mcrypt = array($algorithm_directory, $mode_directory); - if ( $this->mode == CRYPT_DES_MODE_3CBC ) { - $this->des[0]->setMCrypt($algorithm_directory, $mode_directory); - $this->des[1]->setMCrypt($algorithm_directory, $mode_directory); - $this->des[2]->setMCrypt($algorithm_directory, $mode_directory); - } - } - - /** - * Encrypts a message. - * - * @access public - * @param String $plaintext - */ - function encrypt($plaintext) - { - $plaintext = $this->_pad($plaintext); - - // if the key is smaller then 8, do what we'd normally do - if ($this->mode == CRYPT_DES_MODE_3CBC && strlen($this->key) > 8) { - $ciphertext = $this->des[2]->encrypt($this->des[1]->decrypt($this->des[0]->encrypt($plaintext))); - - return $ciphertext; - } - - if ( CRYPT_DES_MODE == CRYPT_DES_MODE_MCRYPT ) { - $td = mcrypt_module_open(MCRYPT_3DES, $this->mcrypt[0], $this->mode, $this->mcrypt[1]); - mcrypt_generic_init($td, $this->key, $this->encryptIV); - - $ciphertext = mcrypt_generic($td, $plaintext); - - mcrypt_generic_deinit($td); - mcrypt_module_close($td); - - if ($this->continuousBuffer) { - $this->encryptIV = substr($ciphertext, -8); - } - - return $ciphertext; - } - - if (strlen($this->key) <= 8) { - $this->des[0]->mode = $this->mode; - - return $this->des[0]->encrypt($plaintext); - } - - // we pad with chr(0) since that's what mcrypt_generic does. to quote from http://php.net/function.mcrypt-generic : - // "The data is padded with "\0" to make sure the length of the data is n * blocksize." - $plaintext = str_pad($plaintext, ceil(strlen($plaintext) / 8) * 8, chr(0)); - - $ciphertext = ''; - switch ($this->mode) { - case CRYPT_DES_MODE_ECB: - for ($i = 0; $i < strlen($plaintext); $i+=8) { - $block = substr($plaintext, $i, 8); - $block = $this->des[0]->_processBlock($block, CRYPT_DES_ENCRYPT); - $block = $this->des[1]->_processBlock($block, CRYPT_DES_DECRYPT); - $block = $this->des[2]->_processBlock($block, CRYPT_DES_ENCRYPT); - $ciphertext.= $block; - } - break; - case CRYPT_DES_MODE_CBC: - $xor = $this->encryptIV; - for ($i = 0; $i < strlen($plaintext); $i+=8) { - $block = substr($plaintext, $i, 8) ^ $xor; - $block = $this->des[0]->_processBlock($block, CRYPT_DES_ENCRYPT); - $block = $this->des[1]->_processBlock($block, CRYPT_DES_DECRYPT); - $block = $this->des[2]->_processBlock($block, CRYPT_DES_ENCRYPT); - $xor = $block; - $ciphertext.= $block; - } - if ($this->continuousBuffer) { - $this->encryptIV = $xor; - } - } - - return $ciphertext; - } - - /** - * Decrypts a message. - * - * @access public - * @param String $ciphertext - */ - function decrypt($ciphertext) - { - if ($this->mode == CRYPT_DES_MODE_3CBC && strlen($this->key) > 8) { - $plaintext = $this->des[0]->decrypt($this->des[1]->encrypt($this->des[2]->decrypt($ciphertext))); - - return $this->_unpad($plaintext); - } - - // we pad with chr(0) since that's what mcrypt_generic does. to quote from http://php.net/function.mcrypt-generic : - // "The data is padded with "\0" to make sure the length of the data is n * blocksize." - $ciphertext = str_pad($ciphertext, (strlen($ciphertext) + 7) & 0xFFFFFFF8, chr(0)); - - if ( CRYPT_DES_MODE == CRYPT_DES_MODE_MCRYPT ) { - $td = mcrypt_module_open(MCRYPT_3DES, $this->mcrypt[0], $this->mode, $this->mcrypt[1]); - mcrypt_generic_init($td, $this->key, $this->decryptIV); - - $plaintext = mdecrypt_generic($td, $ciphertext); - - mcrypt_generic_deinit($td); - mcrypt_module_close($td); - - if ($this->continuousBuffer) { - $this->decryptIV = substr($ciphertext, -8); - } - - return $this->_unpad($plaintext); - } - - if (strlen($this->key) <= 8) { - $this->des[0]->mode = $this->mode; - - return $this->_unpad($this->des[0]->decrypt($plaintext)); - } - - $plaintext = ''; - switch ($this->mode) { - case CRYPT_DES_MODE_ECB: - for ($i = 0; $i < strlen($ciphertext); $i+=8) { - $block = substr($ciphertext, $i, 8); - $block = $this->des[2]->_processBlock($block, CRYPT_DES_DECRYPT); - $block = $this->des[1]->_processBlock($block, CRYPT_DES_ENCRYPT); - $block = $this->des[0]->_processBlock($block, CRYPT_DES_DECRYPT); - $plaintext.= $block; - } - break; - case CRYPT_DES_MODE_CBC: - $xor = $this->decryptIV; - for ($i = 0; $i < strlen($ciphertext); $i+=8) { - $orig = $block = substr($ciphertext, $i, 8); - $block = $this->des[2]->_processBlock($block, CRYPT_DES_DECRYPT); - $block = $this->des[1]->_processBlock($block, CRYPT_DES_ENCRYPT); - $block = $this->des[0]->_processBlock($block, CRYPT_DES_DECRYPT); - $plaintext.= $block ^ $xor; - $xor = $orig; - } - if ($this->continuousBuffer) { - $this->decryptIV = $xor; - } - } - - return $this->_unpad($plaintext); - } - - /** - * Treat consecutive "packets" as if they are a continuous buffer. - * - * Say you have a 16-byte plaintext $plaintext. Using the default behavior, the two following code snippets - * will yield different outputs: - * - * - * echo $des->encrypt(substr($plaintext, 0, 8)); - * echo $des->encrypt(substr($plaintext, 8, 8)); - * - * - * echo $des->encrypt($plaintext); - * - * - * The solution is to enable the continuous buffer. Although this will resolve the above discrepancy, it creates - * another, as demonstrated with the following: - * - * - * $des->encrypt(substr($plaintext, 0, 8)); - * echo $des->decrypt($des->encrypt(substr($plaintext, 8, 8))); - * - * - * echo $des->decrypt($des->encrypt(substr($plaintext, 8, 8))); - * - * - * With the continuous buffer disabled, these would yield the same output. With it enabled, they yield different - * outputs. The reason is due to the fact that the initialization vector's change after every encryption / - * decryption round when the continuous buffer is enabled. When it's disabled, they remain constant. - * - * Put another way, when the continuous buffer is enabled, the state of the Crypt_DES() object changes after each - * encryption / decryption round, whereas otherwise, it'd remain constant. For this reason, it's recommended that - * continuous buffers not be used. They do offer better security and are, in fact, sometimes required (SSH uses them), - * however, they are also less intuitive and more likely to cause you problems. - * - * @see Crypt_TripleDES::disableContinuousBuffer() - * @access public - */ - function enableContinuousBuffer() - { - $this->continuousBuffer = true; - if ($this->mode == CRYPT_DES_MODE_3CBC) { - $this->des[0]->enableContinuousBuffer(); - $this->des[1]->enableContinuousBuffer(); - $this->des[2]->enableContinuousBuffer(); - } - } - - /** - * Treat consecutive packets as if they are a discontinuous buffer. - * - * The default behavior. - * - * @see Crypt_TripleDES::enableContinuousBuffer() - * @access public - */ - function disableContinuousBuffer() - { - $this->continuousBuffer = false; - $this->encryptIV = $this->iv; - $this->decryptIV = $this->iv; - - if ($this->mode == CRYPT_DES_MODE_3CBC) { - $this->des[0]->disableContinuousBuffer(); - $this->des[1]->disableContinuousBuffer(); - $this->des[2]->disableContinuousBuffer(); - } - } - - /** - * Pad "packets". - * - * DES works by encrypting eight bytes at a time. If you ever need to encrypt or decrypt something that's not - * a multiple of eight, it becomes necessary to pad the input so that it's length is a multiple of eight. - * - * Padding is enabled by default. Sometimes, however, it is undesirable to pad strings. Such is the case in SSH1, - * where "packets" are padded with random bytes before being encrypted. Unpad these packets and you risk stripping - * away characters that shouldn't be stripped away. (SSH knows how many bytes are added because the length is - * transmitted separately) - * - * @see Crypt_TripleDES::disablePadding() - * @access public - */ - function enablePadding() - { - $this->padding = true; - } - - /** - * Do not pad packets. - * - * @see Crypt_TripleDES::enablePadding() - * @access public - */ - function disablePadding() - { - $this->padding = false; - } - - /** - * Pads a string - * - * Pads a string using the RSA PKCS padding standards so that its length is a multiple of the blocksize (8). - * 8 - (strlen($text) & 7) bytes are added, each of which is equal to chr(8 - (strlen($text) & 7) - * - * If padding is disabled and $text is not a multiple of the blocksize, the string will be padded regardless - * and padding will, hence forth, be enabled. - * - * @see Crypt_TripleDES::_unpad() - * @access private - */ - function _pad($text) - { - $length = strlen($text); - - if (!$this->padding) { - if (($length & 7) == 0) { - return $text; - } else { - user_error("The plaintext's length ($length) is not a multiple of the block size (8)", E_USER_NOTICE); - $this->padding = true; - } - } - - $pad = 8 - ($length & 7); - return str_pad($text, $length + $pad, chr($pad)); - } - - /** - * Unpads a string - * - * If padding is enabled and the reported padding length is invalid, padding will be, hence forth, disabled. - * - * @see Crypt_TripleDES::_pad() - * @access private - */ - function _unpad($text) - { - if (!$this->padding) { - return $text; - } - - $length = ord($text[strlen($text) - 1]); - - if (!$length || $length > 8) { - user_error("The number of bytes reported as being padded ($length) is invalid (block size = 8)", E_USER_NOTICE); - $this->padding = false; - return $text; - } - - return substr($text, 0, -$length); - } -} - -// vim: ts=4:sw=4:et: + + * setKey('abcdefghijklmnopqrstuvwx'); + * + * $size = 10 * 1024; + * $plaintext = ''; + * for ($i = 0; $i < $size; $i++) { + * $plaintext.= 'a'; + * } + * + * echo $des->decrypt($des->encrypt($plaintext)); + * ?> + * + * + * LICENSE: This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * @category Crypt + * @package Crypt_TripleDES + * @author Jim Wigginton + * @copyright MMVII Jim Wigginton + * @license http://www.gnu.org/licenses/lgpl.txt + * @version $Id: TripleDES.php,v 1.13 2010/02/26 03:40:25 terrafrost Exp $ + * @link http://phpseclib.sourceforge.net + */ + +/** + * Include Crypt_DES + */ +require_once 'DES.php'; + +/** + * Encrypt / decrypt using inner chaining + * + * Inner chaining is used by SSH-1 and is generally considered to be less secure then outer chaining (CRYPT_DES_MODE_CBC3). + */ +define('CRYPT_DES_MODE_3CBC', 3); + +/** + * Encrypt / decrypt using outer chaining + * + * Outer chaining is used by SSH-2 and when the mode is set to CRYPT_DES_MODE_CBC. + */ +define('CRYPT_DES_MODE_CBC3', CRYPT_DES_MODE_CBC); + +/** + * Pure-PHP implementation of Triple DES. + * + * @author Jim Wigginton + * @version 0.1.0 + * @access public + * @package Crypt_TerraDES + */ +class Crypt_TripleDES { + /** + * The Three Keys + * + * @see Crypt_TripleDES::setKey() + * @var String + * @access private + */ + var $key = "\0\0\0\0\0\0\0\0"; + + /** + * The Encryption Mode + * + * @see Crypt_TripleDES::Crypt_TripleDES() + * @var Integer + * @access private + */ + var $mode = CRYPT_DES_MODE_CBC; + + /** + * Continuous Buffer status + * + * @see Crypt_TripleDES::enableContinuousBuffer() + * @var Boolean + * @access private + */ + var $continuousBuffer = false; + + /** + * Padding status + * + * @see Crypt_TripleDES::enablePadding() + * @var Boolean + * @access private + */ + var $padding = true; + + /** + * The Initialization Vector + * + * @see Crypt_TripleDES::setIV() + * @var String + * @access private + */ + var $iv = "\0\0\0\0\0\0\0\0"; + + /** + * A "sliding" Initialization Vector + * + * @see Crypt_TripleDES::enableContinuousBuffer() + * @var String + * @access private + */ + var $encryptIV = "\0\0\0\0\0\0\0\0"; + + /** + * A "sliding" Initialization Vector + * + * @see Crypt_TripleDES::enableContinuousBuffer() + * @var String + * @access private + */ + var $decryptIV = "\0\0\0\0\0\0\0\0"; + + /** + * The Crypt_DES objects + * + * @var Array + * @access private + */ + var $des; + + /** + * mcrypt resource for encryption + * + * The mcrypt resource can be recreated every time something needs to be created or it can be created just once. + * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode. + * + * @see Crypt_AES::encrypt() + * @var String + * @access private + */ + var $enmcrypt; + + /** + * mcrypt resource for decryption + * + * The mcrypt resource can be recreated every time something needs to be created or it can be created just once. + * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode. + * + * @see Crypt_AES::decrypt() + * @var String + * @access private + */ + var $demcrypt; + + /** + * Does the (en|de)mcrypt resource need to be (re)initialized? + * + * @see setKey() + * @see setIV() + * @var Boolean + * @access private + */ + var $changed = true; + + /** + * Default Constructor. + * + * Determines whether or not the mcrypt extension should be used. $mode should only, at present, be + * CRYPT_DES_MODE_ECB or CRYPT_DES_MODE_CBC. If not explictly set, CRYPT_DES_MODE_CBC will be used. + * + * @param optional Integer $mode + * @return Crypt_TripleDES + * @access public + */ + function Crypt_TripleDES($mode = CRYPT_DES_MODE_CBC) + { + if ( !defined('CRYPT_DES_MODE') ) { + switch (true) { + case extension_loaded('mcrypt'): + // i'd check to see if des was supported, by doing in_array('des', mcrypt_list_algorithms('')), + // but since that can be changed after the object has been created, there doesn't seem to be + // a lot of point... + define('CRYPT_DES_MODE', CRYPT_DES_MODE_MCRYPT); + break; + default: + define('CRYPT_DES_MODE', CRYPT_DES_MODE_INTERNAL); + } + } + + if ( $mode == CRYPT_DES_MODE_3CBC ) { + $this->mode = CRYPT_DES_MODE_3CBC; + $this->des = array( + new Crypt_DES(CRYPT_DES_MODE_CBC), + new Crypt_DES(CRYPT_DES_MODE_CBC), + new Crypt_DES(CRYPT_DES_MODE_CBC) + ); + + // we're going to be doing the padding, ourselves, so disable it in the Crypt_DES objects + $this->des[0]->disablePadding(); + $this->des[1]->disablePadding(); + $this->des[2]->disablePadding(); + + return; + } + + switch ( CRYPT_DES_MODE ) { + case CRYPT_DES_MODE_MCRYPT: + switch ($mode) { + case CRYPT_DES_MODE_ECB: + $this->mode = MCRYPT_MODE_ECB; + break; + case CRYPT_DES_MODE_CTR: + $this->mode = 'ctr'; + break; + case CRYPT_DES_MODE_CBC: + default: + $this->mode = MCRYPT_MODE_CBC; + } + + break; + default: + $this->des = array( + new Crypt_DES(CRYPT_DES_MODE_ECB), + new Crypt_DES(CRYPT_DES_MODE_ECB), + new Crypt_DES(CRYPT_DES_MODE_ECB) + ); + + // we're going to be doing the padding, ourselves, so disable it in the Crypt_DES objects + $this->des[0]->disablePadding(); + $this->des[1]->disablePadding(); + $this->des[2]->disablePadding(); + + switch ($mode) { + case CRYPT_DES_MODE_ECB: + case CRYPT_DES_MODE_CTR: + case CRYPT_DES_MODE_CBC: + $this->mode = $mode; + break; + default: + $this->mode = CRYPT_DES_MODE_CBC; + } + } + } + + /** + * Sets the key. + * + * Keys can be of any length. Triple DES, itself, can use 128-bit (eg. strlen($key) == 16) or + * 192-bit (eg. strlen($key) == 24) keys. This function pads and truncates $key as appropriate. + * + * DES also requires that every eighth bit be a parity bit, however, we'll ignore that. + * + * If the key is not explicitly set, it'll be assumed to be all zero's. + * + * @access public + * @param String $key + */ + function setKey($key) + { + $length = strlen($key); + if ($length > 8) { + $key = str_pad($key, 24, chr(0)); + // if $key is between 64 and 128-bits, use the first 64-bits as the last, per this: + // http://php.net/function.mcrypt-encrypt#47973 + //$key = $length <= 16 ? substr_replace($key, substr($key, 0, 8), 16) : substr($key, 0, 24); + } + $this->key = $key; + switch (true) { + case CRYPT_DES_MODE == CRYPT_DES_MODE_INTERNAL: + case $this->mode == CRYPT_DES_MODE_3CBC: + $this->des[0]->setKey(substr($key, 0, 8)); + $this->des[1]->setKey(substr($key, 8, 8)); + $this->des[2]->setKey(substr($key, 16, 8)); + } + $this->changed = true; + } + + /** + * Sets the initialization vector. (optional) + * + * SetIV is not required when CRYPT_DES_MODE_ECB is being used. If not explictly set, it'll be assumed + * to be all zero's. + * + * @access public + * @param String $iv + */ + function setIV($iv) + { + $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($iv, 0, 8), 8, chr(0)); + if ($this->mode == CRYPT_DES_MODE_3CBC) { + $this->des[0]->setIV($iv); + $this->des[1]->setIV($iv); + $this->des[2]->setIV($iv); + } + $this->changed = true; + } + + /** + * Generate CTR XOR encryption key + * + * Encrypt the output of this and XOR it against the ciphertext / plaintext to get the + * plaintext / ciphertext in CTR mode. + * + * @see Crypt_DES::decrypt() + * @see Crypt_DES::encrypt() + * @access public + * @param Integer $length + * @param String $iv + */ + function _generate_xor($length, &$iv) + { + $xor = ''; + $num_blocks = ($length + 7) >> 3; + for ($i = 0; $i < $num_blocks; $i++) { + $xor.= $iv; + for ($j = 4; $j <= 8; $j+=4) { + $temp = substr($iv, -$j, 4); + switch ($temp) { + case "\xFF\xFF\xFF\xFF": + $iv = substr_replace($iv, "\x00\x00\x00\x00", -$j, 4); + break; + case "\x7F\xFF\xFF\xFF": + $iv = substr_replace($iv, "\x80\x00\x00\x00", -$j, 4); + break 2; + default: + extract(unpack('Ncount', $temp)); + $iv = substr_replace($iv, pack('N', $count + 1), -$j, 4); + break 2; + } + } + } + + return $xor; + } + + /** + * Encrypts a message. + * + * @access public + * @param String $plaintext + */ + function encrypt($plaintext) + { + if ($this->mode != CRYPT_DES_MODE_CTR && $this->mode != 'ctr') { + $plaintext = $this->_pad($plaintext); + } + + // if the key is smaller then 8, do what we'd normally do + if ($this->mode == CRYPT_DES_MODE_3CBC && strlen($this->key) > 8) { + $ciphertext = $this->des[2]->encrypt($this->des[1]->decrypt($this->des[0]->encrypt($plaintext))); + + return $ciphertext; + } + + if ( CRYPT_DES_MODE == CRYPT_DES_MODE_MCRYPT ) { + if ($this->changed) { + if (!isset($this->enmcrypt)) { + $this->enmcrypt = mcrypt_module_open(MCRYPT_3DES, '', $this->mode, ''); + } + mcrypt_generic_init($this->enmcrypt, $this->key, $this->encryptIV); + $this->changed = false; + } + + $ciphertext = mcrypt_generic($this->enmcrypt, $plaintext); + + if (!$this->continuousBuffer) { + mcrypt_generic_init($this->enmcrypt, $this->key, $this->encryptIV); + } + + return $ciphertext; + } + + if (strlen($this->key) <= 8) { + $this->des[0]->mode = $this->mode; + + return $this->des[0]->encrypt($plaintext); + } + + // we pad with chr(0) since that's what mcrypt_generic does. to quote from http://php.net/function.mcrypt-generic : + // "The data is padded with "\0" to make sure the length of the data is n * blocksize." + $plaintext = str_pad($plaintext, ceil(strlen($plaintext) / 8) * 8, chr(0)); + + $des = $this->des; + + $ciphertext = ''; + switch ($this->mode) { + case CRYPT_DES_MODE_ECB: + for ($i = 0; $i < strlen($plaintext); $i+=8) { + $block = substr($plaintext, $i, 8); + $block = $des[0]->_processBlock($block, CRYPT_DES_ENCRYPT); + $block = $des[1]->_processBlock($block, CRYPT_DES_DECRYPT); + $block = $des[2]->_processBlock($block, CRYPT_DES_ENCRYPT); + $ciphertext.= $block; + } + break; + case CRYPT_DES_MODE_CBC: + $xor = $this->encryptIV; + for ($i = 0; $i < strlen($plaintext); $i+=8) { + $block = substr($plaintext, $i, 8) ^ $xor; + $block = $des[0]->_processBlock($block, CRYPT_DES_ENCRYPT); + $block = $des[1]->_processBlock($block, CRYPT_DES_DECRYPT); + $block = $des[2]->_processBlock($block, CRYPT_DES_ENCRYPT); + $xor = $block; + $ciphertext.= $block; + } + if ($this->continuousBuffer) { + $this->encryptIV = $xor; + } + break; + case CRYPT_DES_MODE_CTR: + $xor = $this->encryptIV; + for ($i = 0; $i < strlen($plaintext); $i+=8) { + $key = $this->_generate_xor(8, $xor); + $key = $des[0]->_processBlock($key, CRYPT_DES_ENCRYPT); + $key = $des[1]->_processBlock($key, CRYPT_DES_DECRYPT); + $key = $des[2]->_processBlock($key, CRYPT_DES_ENCRYPT); + $block = substr($plaintext, $i, 8); + $ciphertext.= $block ^ $key; + } + if ($this->continuousBuffer) { + $this->encryptIV = $xor; + } + } + + return $ciphertext; + } + + /** + * Decrypts a message. + * + * @access public + * @param String $ciphertext + */ + function decrypt($ciphertext) + { + if ($this->mode == CRYPT_DES_MODE_3CBC && strlen($this->key) > 8) { + $plaintext = $this->des[0]->decrypt($this->des[1]->encrypt($this->des[2]->decrypt($ciphertext))); + + return $this->_unpad($plaintext); + } + + // we pad with chr(0) since that's what mcrypt_generic does. to quote from http://php.net/function.mcrypt-generic : + // "The data is padded with "\0" to make sure the length of the data is n * blocksize." + $ciphertext = str_pad($ciphertext, (strlen($ciphertext) + 7) & 0xFFFFFFF8, chr(0)); + + if ( CRYPT_DES_MODE == CRYPT_DES_MODE_MCRYPT ) { + if ($this->changed) { + if (!isset($this->demcrypt)) { + $this->demcrypt = mcrypt_module_open(MCRYPT_3DES, '', $this->mode, ''); + } + mcrypt_generic_init($this->demcrypt, $this->key, $this->decryptIV); + $this->changed = false; + } + + $plaintext = mdecrypt_generic($this->demcrypt, $ciphertext); + + if (!$this->continuousBuffer) { + mcrypt_generic_init($this->demcrypt, $this->key, $this->decryptIV); + } + + return $this->mode != 'ctr' ? $this->_unpad($plaintext) : $plaintext; + } + + if (strlen($this->key) <= 8) { + $this->des[0]->mode = $this->mode; + + return $this->_unpad($this->des[0]->decrypt($plaintext)); + } + + $des = $this->des; + + $plaintext = ''; + switch ($this->mode) { + case CRYPT_DES_MODE_ECB: + for ($i = 0; $i < strlen($ciphertext); $i+=8) { + $block = substr($ciphertext, $i, 8); + $block = $des[2]->_processBlock($block, CRYPT_DES_DECRYPT); + $block = $des[1]->_processBlock($block, CRYPT_DES_ENCRYPT); + $block = $des[0]->_processBlock($block, CRYPT_DES_DECRYPT); + $plaintext.= $block; + } + break; + case CRYPT_DES_MODE_CBC: + $xor = $this->decryptIV; + for ($i = 0; $i < strlen($ciphertext); $i+=8) { + $orig = $block = substr($ciphertext, $i, 8); + $block = $des[2]->_processBlock($block, CRYPT_DES_DECRYPT); + $block = $des[1]->_processBlock($block, CRYPT_DES_ENCRYPT); + $block = $des[0]->_processBlock($block, CRYPT_DES_DECRYPT); + $plaintext.= $block ^ $xor; + $xor = $orig; + } + if ($this->continuousBuffer) { + $this->decryptIV = $xor; + } + break; + case CRYPT_DES_MODE_CTR: + $xor = $this->decryptIV; + for ($i = 0; $i < strlen($ciphertext); $i+=8) { + $key = $this->_generate_xor(8, $xor); + $key = $des[0]->_processBlock($key, CRYPT_DES_ENCRYPT); + $key = $des[1]->_processBlock($key, CRYPT_DES_DECRYPT); + $key = $des[2]->_processBlock($key, CRYPT_DES_ENCRYPT); + $block = substr($ciphertext, $i, 8); + $plaintext.= $block ^ $key; + } + if ($this->continuousBuffer) { + $this->decryptIV = $xor; + } + } + + return $this->mode != CRYPT_DES_MODE_CTR ? $this->_unpad($plaintext) : $plaintext; + } + + /** + * Treat consecutive "packets" as if they are a continuous buffer. + * + * Say you have a 16-byte plaintext $plaintext. Using the default behavior, the two following code snippets + * will yield different outputs: + * + * + * echo $des->encrypt(substr($plaintext, 0, 8)); + * echo $des->encrypt(substr($plaintext, 8, 8)); + * + * + * echo $des->encrypt($plaintext); + * + * + * The solution is to enable the continuous buffer. Although this will resolve the above discrepancy, it creates + * another, as demonstrated with the following: + * + * + * $des->encrypt(substr($plaintext, 0, 8)); + * echo $des->decrypt($des->encrypt(substr($plaintext, 8, 8))); + * + * + * echo $des->decrypt($des->encrypt(substr($plaintext, 8, 8))); + * + * + * With the continuous buffer disabled, these would yield the same output. With it enabled, they yield different + * outputs. The reason is due to the fact that the initialization vector's change after every encryption / + * decryption round when the continuous buffer is enabled. When it's disabled, they remain constant. + * + * Put another way, when the continuous buffer is enabled, the state of the Crypt_DES() object changes after each + * encryption / decryption round, whereas otherwise, it'd remain constant. For this reason, it's recommended that + * continuous buffers not be used. They do offer better security and are, in fact, sometimes required (SSH uses them), + * however, they are also less intuitive and more likely to cause you problems. + * + * @see Crypt_TripleDES::disableContinuousBuffer() + * @access public + */ + function enableContinuousBuffer() + { + $this->continuousBuffer = true; + if ($this->mode == CRYPT_DES_MODE_3CBC) { + $this->des[0]->enableContinuousBuffer(); + $this->des[1]->enableContinuousBuffer(); + $this->des[2]->enableContinuousBuffer(); + } + } + + /** + * Treat consecutive packets as if they are a discontinuous buffer. + * + * The default behavior. + * + * @see Crypt_TripleDES::enableContinuousBuffer() + * @access public + */ + function disableContinuousBuffer() + { + $this->continuousBuffer = false; + $this->encryptIV = $this->iv; + $this->decryptIV = $this->iv; + + if ($this->mode == CRYPT_DES_MODE_3CBC) { + $this->des[0]->disableContinuousBuffer(); + $this->des[1]->disableContinuousBuffer(); + $this->des[2]->disableContinuousBuffer(); + } + } + + /** + * Pad "packets". + * + * DES works by encrypting eight bytes at a time. If you ever need to encrypt or decrypt something that's not + * a multiple of eight, it becomes necessary to pad the input so that it's length is a multiple of eight. + * + * Padding is enabled by default. Sometimes, however, it is undesirable to pad strings. Such is the case in SSH1, + * where "packets" are padded with random bytes before being encrypted. Unpad these packets and you risk stripping + * away characters that shouldn't be stripped away. (SSH knows how many bytes are added because the length is + * transmitted separately) + * + * @see Crypt_TripleDES::disablePadding() + * @access public + */ + function enablePadding() + { + $this->padding = true; + } + + /** + * Do not pad packets. + * + * @see Crypt_TripleDES::enablePadding() + * @access public + */ + function disablePadding() + { + $this->padding = false; + } + + /** + * Pads a string + * + * Pads a string using the RSA PKCS padding standards so that its length is a multiple of the blocksize (8). + * 8 - (strlen($text) & 7) bytes are added, each of which is equal to chr(8 - (strlen($text) & 7) + * + * If padding is disabled and $text is not a multiple of the blocksize, the string will be padded regardless + * and padding will, hence forth, be enabled. + * + * @see Crypt_TripleDES::_unpad() + * @access private + */ + function _pad($text) + { + $length = strlen($text); + + if (!$this->padding) { + if (($length & 7) == 0) { + return $text; + } else { + user_error("The plaintext's length ($length) is not a multiple of the block size (8)", E_USER_NOTICE); + $this->padding = true; + } + } + + $pad = 8 - ($length & 7); + return str_pad($text, $length + $pad, chr($pad)); + } + + /** + * Unpads a string + * + * If padding is enabled and the reported padding length is invalid the encryption key will be assumed to be wrong + * and false will be returned. + * + * @see Crypt_TripleDES::_pad() + * @access private + */ + function _unpad($text) + { + if (!$this->padding) { + return $text; + } + + $length = ord($text[strlen($text) - 1]); + + if (!$length || $length > 8) { + return false; + } + + return substr($text, 0, -$length); + } +} + +// vim: ts=4:sw=4:et: // vim6: fdl=1: \ No newline at end of file diff --git a/plugins/OStatus/extlib/Math/BigInteger.php b/plugins/OStatus/extlib/Math/BigInteger.php index ce0e08354..9733351d4 100644 --- a/plugins/OStatus/extlib/Math/BigInteger.php +++ b/plugins/OStatus/extlib/Math/BigInteger.php @@ -1,3060 +1,3545 @@ -> and << cannot be used, nor can the modulo operator %, - * which only supports integers. Although this fact will slow this library down, the fact that such a high - * base is being used should more than compensate. - * - * When PHP version 6 is officially released, we'll be able to use 64-bit integers. This should, once again, - * allow bitwise operators, and will increase the maximum possible base to 2**31 (or 2**62 for addition / - * subtraction). - * - * Useful resources are as follows: - * - * - {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf Handbook of Applied Cryptography (HAC)} - * - {@link http://math.libtomcrypt.com/files/tommath.pdf Multi-Precision Math (MPM)} - * - Java's BigInteger classes. See /j2se/src/share/classes/java/math in jdk-1_5_0-src-jrl.zip - * - * One idea for optimization is to use the comba method to reduce the number of operations performed. - * MPM uses this quite extensively. The following URL elaborates: - * - * {@link http://www.everything2.com/index.pl?node_id=1736418}}} - * - * Here's an example of how to use this library: - * - * add($b); - * - * echo $c->toString(); // outputs 5 - * ?> - * - * - * LICENSE: This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, - * MA 02111-1307 USA - * - * @category Math - * @package Math_BigInteger - * @author Jim Wigginton - * @copyright MMVI Jim Wigginton - * @license http://www.gnu.org/licenses/lgpl.txt - * @version $Id: BigInteger.php,v 1.18 2009/12/04 19:12:18 terrafrost Exp $ - * @link http://pear.php.net/package/Math_BigInteger - */ - -/**#@+ - * @access private - * @see Math_BigInteger::_slidingWindow() - */ -/** - * @see Math_BigInteger::_montgomery() - * @see Math_BigInteger::_prepMontgomery() - */ -define('MATH_BIGINTEGER_MONTGOMERY', 0); -/** - * @see Math_BigInteger::_barrett() - */ -define('MATH_BIGINTEGER_BARRETT', 1); -/** - * @see Math_BigInteger::_mod2() - */ -define('MATH_BIGINTEGER_POWEROF2', 2); -/** - * @see Math_BigInteger::_remainder() - */ -define('MATH_BIGINTEGER_CLASSIC', 3); -/** - * @see Math_BigInteger::__clone() - */ -define('MATH_BIGINTEGER_NONE', 4); -/**#@-*/ - -/**#@+ - * @access private - * @see Math_BigInteger::_montgomery() - * @see Math_BigInteger::_barrett() - */ -/** - * $cache[MATH_BIGINTEGER_VARIABLE] tells us whether or not the cached data is still valid. - */ -define('MATH_BIGINTEGER_VARIABLE', 0); -/** - * $cache[MATH_BIGINTEGER_DATA] contains the cached data. - */ -define('MATH_BIGINTEGER_DATA', 1); -/**#@-*/ - -/**#@+ - * @access private - * @see Math_BigInteger::Math_BigInteger() - */ -/** - * To use the pure-PHP implementation - */ -define('MATH_BIGINTEGER_MODE_INTERNAL', 1); -/** - * To use the BCMath library - * - * (if enabled; otherwise, the internal implementation will be used) - */ -define('MATH_BIGINTEGER_MODE_BCMATH', 2); -/** - * To use the GMP library - * - * (if present; otherwise, either the BCMath or the internal implementation will be used) - */ -define('MATH_BIGINTEGER_MODE_GMP', 3); -/**#@-*/ - -/** - * The largest digit that may be used in addition / subtraction - * - * (we do pow(2, 52) instead of using 4503599627370496, directly, because some PHP installations - * will truncate 4503599627370496) - * - * @access private - */ -define('MATH_BIGINTEGER_MAX_DIGIT52', pow(2, 52)); - -/** - * Karatsuba Cutoff - * - * At what point do we switch between Karatsuba multiplication and schoolbook long multiplication? - * - * @access private - */ -define('MATH_BIGINTEGER_KARATSUBA_CUTOFF', 15); - -/** - * Pure-PHP arbitrary precision integer arithmetic library. Supports base-2, base-10, base-16, and base-256 - * numbers. - * - * @author Jim Wigginton - * @version 1.0.0RC3 - * @access public - * @package Math_BigInteger - */ -class Math_BigInteger { - /** - * Holds the BigInteger's value. - * - * @var Array - * @access private - */ - var $value; - - /** - * Holds the BigInteger's magnitude. - * - * @var Boolean - * @access private - */ - var $is_negative = false; - - /** - * Random number generator function - * - * @see setRandomGenerator() - * @access private - */ - var $generator = 'mt_rand'; - - /** - * Precision - * - * @see setPrecision() - * @access private - */ - var $precision = -1; - - /** - * Precision Bitmask - * - * @see setPrecision() - * @access private - */ - var $bitmask = false; - - /** - * Converts base-2, base-10, base-16, and binary strings (eg. base-256) to BigIntegers. - * - * If the second parameter - $base - is negative, then it will be assumed that the number's are encoded using - * two's compliment. The sole exception to this is -10, which is treated the same as 10 is. - * - * Here's an example: - * - * toString(); // outputs 50 - * ?> - * - * - * @param optional $x base-10 number or base-$base number if $base set. - * @param optional integer $base - * @return Math_BigInteger - * @access public - */ - function Math_BigInteger($x = 0, $base = 10) - { - if ( !defined('MATH_BIGINTEGER_MODE') ) { - switch (true) { - case extension_loaded('gmp'): - define('MATH_BIGINTEGER_MODE', MATH_BIGINTEGER_MODE_GMP); - break; - case extension_loaded('bcmath'): - define('MATH_BIGINTEGER_MODE', MATH_BIGINTEGER_MODE_BCMATH); - break; - default: - define('MATH_BIGINTEGER_MODE', MATH_BIGINTEGER_MODE_INTERNAL); - } - } - - switch ( MATH_BIGINTEGER_MODE ) { - case MATH_BIGINTEGER_MODE_GMP: - if (is_resource($x) && get_resource_type($x) == 'GMP integer') { - $this->value = $x; - return; - } - $this->value = gmp_init(0); - break; - case MATH_BIGINTEGER_MODE_BCMATH: - $this->value = '0'; - break; - default: - $this->value = array(); - } - - if ($x === 0) { - return; - } - - switch ($base) { - case -256: - if (ord($x[0]) & 0x80) { - $x = ~$x; - $this->is_negative = true; - } - case 256: - switch ( MATH_BIGINTEGER_MODE ) { - case MATH_BIGINTEGER_MODE_GMP: - $sign = $this->is_negative ? '-' : ''; - $this->value = gmp_init($sign . '0x' . bin2hex($x)); - break; - case MATH_BIGINTEGER_MODE_BCMATH: - // round $len to the nearest 4 (thanks, DavidMJ!) - $len = (strlen($x) + 3) & 0xFFFFFFFC; - - $x = str_pad($x, $len, chr(0), STR_PAD_LEFT); - - for ($i = 0; $i < $len; $i+= 4) { - $this->value = bcmul($this->value, '4294967296'); // 4294967296 == 2**32 - $this->value = bcadd($this->value, 0x1000000 * ord($x[$i]) + ((ord($x[$i + 1]) << 16) | (ord($x[$i + 2]) << 8) | ord($x[$i + 3]))); - } - - if ($this->is_negative) { - $this->value = '-' . $this->value; - } - - break; - // converts a base-2**8 (big endian / msb) number to base-2**26 (little endian / lsb) - default: - while (strlen($x)) { - $this->value[] = $this->_bytes2int($this->_base256_rshift($x, 26)); - } - } - - if ($this->is_negative) { - if (MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_INTERNAL) { - $this->is_negative = false; - } - $temp = $this->add(new Math_BigInteger('-1')); - $this->value = $temp->value; - } - break; - case 16: - case -16: - if ($base > 0 && $x[0] == '-') { - $this->is_negative = true; - $x = substr($x, 1); - } - - $x = preg_replace('#^(?:0x)?([A-Fa-f0-9]*).*#', '$1', $x); - - $is_negative = false; - if ($base < 0 && hexdec($x[0]) >= 8) { - $this->is_negative = $is_negative = true; - $x = bin2hex(~pack('H*', $x)); - } - - switch ( MATH_BIGINTEGER_MODE ) { - case MATH_BIGINTEGER_MODE_GMP: - $temp = $this->is_negative ? '-0x' . $x : '0x' . $x; - $this->value = gmp_init($temp); - $this->is_negative = false; - break; - case MATH_BIGINTEGER_MODE_BCMATH: - $x = ( strlen($x) & 1 ) ? '0' . $x : $x; - $temp = new Math_BigInteger(pack('H*', $x), 256); - $this->value = $this->is_negative ? '-' . $temp->value : $temp->value; - $this->is_negative = false; - break; - default: - $x = ( strlen($x) & 1 ) ? '0' . $x : $x; - $temp = new Math_BigInteger(pack('H*', $x), 256); - $this->value = $temp->value; - } - - if ($is_negative) { - $temp = $this->add(new Math_BigInteger('-1')); - $this->value = $temp->value; - } - break; - case 10: - case -10: - $x = preg_replace('#^(-?[0-9]*).*#', '$1', $x); - - switch ( MATH_BIGINTEGER_MODE ) { - case MATH_BIGINTEGER_MODE_GMP: - $this->value = gmp_init($x); - break; - case MATH_BIGINTEGER_MODE_BCMATH: - // explicitly casting $x to a string is necessary, here, since doing $x[0] on -1 yields different - // results then doing it on '-1' does (modInverse does $x[0]) - $this->value = (string) $x; - break; - default: - $temp = new Math_BigInteger(); - - // array(10000000) is 10**7 in base-2**26. 10**7 is the closest to 2**26 we can get without passing it. - $multiplier = new Math_BigInteger(); - $multiplier->value = array(10000000); - - if ($x[0] == '-') { - $this->is_negative = true; - $x = substr($x, 1); - } - - $x = str_pad($x, strlen($x) + (6 * strlen($x)) % 7, 0, STR_PAD_LEFT); - - while (strlen($x)) { - $temp = $temp->multiply($multiplier); - $temp = $temp->add(new Math_BigInteger($this->_int2bytes(substr($x, 0, 7)), 256)); - $x = substr($x, 7); - } - - $this->value = $temp->value; - } - break; - case 2: // base-2 support originally implemented by Lluis Pamies - thanks! - case -2: - if ($base > 0 && $x[0] == '-') { - $this->is_negative = true; - $x = substr($x, 1); - } - - $x = preg_replace('#^([01]*).*#', '$1', $x); - $x = str_pad($x, strlen($x) + (3 * strlen($x)) % 4, 0, STR_PAD_LEFT); - - $str = '0x'; - while (strlen($x)) { - $part = substr($x, 0, 4); - $str.= dechex(bindec($part)); - $x = substr($x, 4); - } - - if ($this->is_negative) { - $str = '-' . $str; - } - - $temp = new Math_BigInteger($str, 8 * $base); // ie. either -16 or +16 - $this->value = $temp->value; - $this->is_negative = $temp->is_negative; - - break; - default: - // base not supported, so we'll let $this == 0 - } - } - - /** - * Converts a BigInteger to a byte string (eg. base-256). - * - * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're - * saved as two's compliment. - * - * Here's an example: - * - * toBytes(); // outputs chr(65) - * ?> - * - * - * @param Boolean $twos_compliment - * @return String - * @access public - * @internal Converts a base-2**26 number to base-2**8 - */ - function toBytes($twos_compliment = false) - { - if ($twos_compliment) { - $comparison = $this->compare(new Math_BigInteger()); - if ($comparison == 0) { - return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; - } - - $temp = $comparison < 0 ? $this->add(new Math_BigInteger(1)) : $this->copy(); - $bytes = $temp->toBytes(); - - if (empty($bytes)) { // eg. if the number we're trying to convert is -1 - $bytes = chr(0); - } - - if (ord($bytes[0]) & 0x80) { - $bytes = chr(0) . $bytes; - } - - return $comparison < 0 ? ~$bytes : $bytes; - } - - switch ( MATH_BIGINTEGER_MODE ) { - case MATH_BIGINTEGER_MODE_GMP: - if (gmp_cmp($this->value, gmp_init(0)) == 0) { - return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; - } - - $temp = gmp_strval(gmp_abs($this->value), 16); - $temp = ( strlen($temp) & 1 ) ? '0' . $temp : $temp; - $temp = pack('H*', $temp); - - return $this->precision > 0 ? - substr(str_pad($temp, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) : - ltrim($temp, chr(0)); - case MATH_BIGINTEGER_MODE_BCMATH: - if ($this->value === '0') { - return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; - } - - $value = ''; - $current = $this->value; - - if ($current[0] == '-') { - $current = substr($current, 1); - } - - // we don't do four bytes at a time because then numbers larger than 1<<31 would be negative - // two's complimented numbers, which would break chr. - while (bccomp($current, '0') > 0) { - $temp = bcmod($current, 0x1000000); - $value = chr($temp >> 16) . chr($temp >> 8) . chr($temp) . $value; - $current = bcdiv($current, 0x1000000); - } - - return $this->precision > 0 ? - substr(str_pad($value, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) : - ltrim($value, chr(0)); - } - - if (!count($this->value)) { - return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; - } - $result = $this->_int2bytes($this->value[count($this->value) - 1]); - - $temp = $this->copy(); - - for ($i = count($temp->value) - 2; $i >= 0; $i--) { - $temp->_base256_lshift($result, 26); - $result = $result | str_pad($temp->_int2bytes($temp->value[$i]), strlen($result), chr(0), STR_PAD_LEFT); - } - - return $this->precision > 0 ? - substr(str_pad($result, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) : - $result; - } - - /** - * Converts a BigInteger to a hex string (eg. base-16)). - * - * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're - * saved as two's compliment. - * - * Here's an example: - * - * toHex(); // outputs '41' - * ?> - * - * - * @param Boolean $twos_compliment - * @return String - * @access public - * @internal Converts a base-2**26 number to base-2**8 - */ - function toHex($twos_compliment = false) - { - return bin2hex($this->toBytes($twos_compliment)); - } - - /** - * Converts a BigInteger to a bit string (eg. base-2). - * - * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're - * saved as two's compliment. - * - * Here's an example: - * - * toBits(); // outputs '1000001' - * ?> - * - * - * @param Boolean $twos_compliment - * @return String - * @access public - * @internal Converts a base-2**26 number to base-2**2 - */ - function toBits($twos_compliment = false) - { - $hex = $this->toHex($twos_compliment); - $bits = ''; - for ($i = 0; $i < strlen($hex); $i+=8) { - $bits.= str_pad(decbin(hexdec(substr($hex, $i, 8))), 32, '0', STR_PAD_LEFT); - } - return $this->precision > 0 ? substr($bits, -$this->precision) : ltrim($bits, '0'); - } - - /** - * Converts a BigInteger to a base-10 number. - * - * Here's an example: - * - * toString(); // outputs 50 - * ?> - * - * - * @return String - * @access public - * @internal Converts a base-2**26 number to base-10**7 (which is pretty much base-10) - */ - function toString() - { - switch ( MATH_BIGINTEGER_MODE ) { - case MATH_BIGINTEGER_MODE_GMP: - return gmp_strval($this->value); - case MATH_BIGINTEGER_MODE_BCMATH: - if ($this->value === '0') { - return '0'; - } - - return ltrim($this->value, '0'); - } - - if (!count($this->value)) { - return '0'; - } - - $temp = $this->copy(); - $temp->is_negative = false; - - $divisor = new Math_BigInteger(); - $divisor->value = array(10000000); // eg. 10**7 - $result = ''; - while (count($temp->value)) { - list($temp, $mod) = $temp->divide($divisor); - $result = str_pad($mod->value[0], 7, '0', STR_PAD_LEFT) . $result; - } - $result = ltrim($result, '0'); - - if ($this->is_negative) { - $result = '-' . $result; - } - - return $result; - } - - /** - * Copy an object - * - * PHP5 passes objects by reference while PHP4 passes by value. As such, we need a function to guarantee - * that all objects are passed by value, when appropriate. More information can be found here: - * - * {@link http://php.net/language.oop5.basic#51624} - * - * @access public - * @see __clone() - * @return Math_BigInteger - */ - function copy() - { - $temp = new Math_BigInteger(); - $temp->value = $this->value; - $temp->is_negative = $this->is_negative; - $temp->generator = $this->generator; - $temp->precision = $this->precision; - $temp->bitmask = $this->bitmask; - return $temp; - } - - /** - * __toString() magic method - * - * Will be called, automatically, if you're supporting just PHP5. If you're supporting PHP4, you'll need to call - * toString(). - * - * @access public - * @internal Implemented per a suggestion by Techie-Michael - thanks! - */ - function __toString() - { - return $this->toString(); - } - - /** - * __clone() magic method - * - * Although you can call Math_BigInteger::__toString() directly in PHP5, you cannot call Math_BigInteger::__clone() - * directly in PHP5. You can in PHP4 since it's not a magic method, but in PHP5, you have to call it by using the PHP5 - * only syntax of $y = clone $x. As such, if you're trying to write an application that works on both PHP4 and PHP5, - * call Math_BigInteger::copy(), instead. - * - * @access public - * @see copy() - * @return Math_BigInteger - */ - function __clone() - { - return $this->copy(); - } - - /** - * Adds two BigIntegers. - * - * Here's an example: - * - * add($b); - * - * echo $c->toString(); // outputs 30 - * ?> - * - * - * @param Math_BigInteger $y - * @return Math_BigInteger - * @access public - * @internal Performs base-2**52 addition - */ - function add($y) - { - switch ( MATH_BIGINTEGER_MODE ) { - case MATH_BIGINTEGER_MODE_GMP: - $temp = new Math_BigInteger(); - $temp->value = gmp_add($this->value, $y->value); - - return $this->_normalize($temp); - case MATH_BIGINTEGER_MODE_BCMATH: - $temp = new Math_BigInteger(); - $temp->value = bcadd($this->value, $y->value); - - return $this->_normalize($temp); - } - - $this_size = count($this->value); - $y_size = count($y->value); - - if ($this_size == 0) { - return $y->copy(); - } else if ($y_size == 0) { - return $this->copy(); - } - - // subtract, if appropriate - if ( $this->is_negative != $y->is_negative ) { - // is $y the negative number? - $y_negative = $this->compare($y) > 0; - - $temp = $this->copy(); - $y = $y->copy(); - $temp->is_negative = $y->is_negative = false; - - $diff = $temp->compare($y); - if ( !$diff ) { - $temp = new Math_BigInteger(); - return $this->_normalize($temp); - } - - $temp = $temp->subtract($y); - - $temp->is_negative = ($diff > 0) ? !$y_negative : $y_negative; - - return $this->_normalize($temp); - } - - $result = new Math_BigInteger(); - $carry = 0; - - $size = max($this_size, $y_size); - $size+= $size & 1; // rounds $size to the nearest 2. - - $x = array_pad($this->value, $size, 0); - $y = array_pad($y->value, $size, 0); - - for ($i = 0; $i < $size - 1; $i+=2) { - $sum = $x[$i + 1] * 0x4000000 + $x[$i] + $y[$i + 1] * 0x4000000 + $y[$i] + $carry; - $carry = $sum >= MATH_BIGINTEGER_MAX_DIGIT52; // eg. floor($sum / 2**52); only possible values (in any base) are 0 and 1 - $sum = $carry ? $sum - MATH_BIGINTEGER_MAX_DIGIT52 : $sum; - - $temp = floor($sum / 0x4000000); - - $result->value[] = $sum - 0x4000000 * $temp; // eg. a faster alternative to fmod($sum, 0x4000000) - $result->value[] = $temp; - } - - if ($carry) { - $result->value[] = (int) $carry; - } - - $result->is_negative = $this->is_negative; - - return $this->_normalize($result); - } - - /** - * Subtracts two BigIntegers. - * - * Here's an example: - * - * subtract($b); - * - * echo $c->toString(); // outputs -10 - * ?> - * - * - * @param Math_BigInteger $y - * @return Math_BigInteger - * @access public - * @internal Performs base-2**52 subtraction - */ - function subtract($y) - { - switch ( MATH_BIGINTEGER_MODE ) { - case MATH_BIGINTEGER_MODE_GMP: - $temp = new Math_BigInteger(); - $temp->value = gmp_sub($this->value, $y->value); - - return $this->_normalize($temp); - case MATH_BIGINTEGER_MODE_BCMATH: - $temp = new Math_BigInteger(); - $temp->value = bcsub($this->value, $y->value); - - return $this->_normalize($temp); - } - - $this_size = count($this->value); - $y_size = count($y->value); - - if ($this_size == 0) { - $temp = $y->copy(); - $temp->is_negative = !$temp->is_negative; - return $temp; - } else if ($y_size == 0) { - return $this->copy(); - } - - // add, if appropriate (ie. -$x - +$y or +$x - -$y) - if ( $this->is_negative != $y->is_negative ) { - $is_negative = $y->compare($this) > 0; - - $temp = $this->copy(); - $y = $y->copy(); - $temp->is_negative = $y->is_negative = false; - - $temp = $temp->add($y); - - $temp->is_negative = $is_negative; - - return $this->_normalize($temp); - } - - $diff = $this->compare($y); - - if ( !$diff ) { - $temp = new Math_BigInteger(); - return $this->_normalize($temp); - } - - // switch $this and $y around, if appropriate. - if ( (!$this->is_negative && $diff < 0) || ($this->is_negative && $diff > 0) ) { - $is_negative = $y->is_negative; - - $temp = $this->copy(); - $y = $y->copy(); - $temp->is_negative = $y->is_negative = false; - - $temp = $y->subtract($temp); - $temp->is_negative = !$is_negative; - - return $this->_normalize($temp); - } - - $result = new Math_BigInteger(); - $carry = 0; - - $size = max($this_size, $y_size); - $size+= $size % 2; - - $x = array_pad($this->value, $size, 0); - $y = array_pad($y->value, $size, 0); - - for ($i = 0; $i < $size - 1; $i+=2) { - $sum = $x[$i + 1] * 0x4000000 + $x[$i] - $y[$i + 1] * 0x4000000 - $y[$i] + $carry; - $carry = $sum < 0 ? -1 : 0; // eg. floor($sum / 2**52); only possible values (in any base) are 0 and 1 - $sum = $carry ? $sum + MATH_BIGINTEGER_MAX_DIGIT52 : $sum; - - $temp = floor($sum / 0x4000000); - - $result->value[] = $sum - 0x4000000 * $temp; - $result->value[] = $temp; - } - - // $carry shouldn't be anything other than zero, at this point, since we already made sure that $this - // was bigger than $y. - - $result->is_negative = $this->is_negative; - - return $this->_normalize($result); - } - - /** - * Multiplies two BigIntegers - * - * Here's an example: - * - * multiply($b); - * - * echo $c->toString(); // outputs 200 - * ?> - * - * - * @param Math_BigInteger $x - * @return Math_BigInteger - * @access public - */ - function multiply($x) - { - switch ( MATH_BIGINTEGER_MODE ) { - case MATH_BIGINTEGER_MODE_GMP: - $temp = new Math_BigInteger(); - $temp->value = gmp_mul($this->value, $x->value); - - return $this->_normalize($temp); - case MATH_BIGINTEGER_MODE_BCMATH: - $temp = new Math_BigInteger(); - $temp->value = bcmul($this->value, $x->value); - - return $this->_normalize($temp); - } - - static $cutoff = false; - if ($cutoff === false) { - $cutoff = 2 * MATH_BIGINTEGER_KARATSUBA_CUTOFF; - } - - if ( $this->equals($x) ) { - return $this->_square(); - } - - $this_length = count($this->value); - $x_length = count($x->value); - - if ( !$this_length || !$x_length ) { // a 0 is being multiplied - $temp = new Math_BigInteger(); - return $this->_normalize($temp); - } - - $product = min($this_length, $x_length) < $cutoff ? $this->_multiply($x) : $this->_karatsuba($x); - - $product->is_negative = $this->is_negative != $x->is_negative; - - return $this->_normalize($product); - } - - /** - * Performs long multiplication up to $stop digits - * - * If you're going to be doing array_slice($product->value, 0, $stop), some cycles can be saved. - * - * @see _barrett() - * @param Math_BigInteger $x - * @return Math_BigInteger - * @access private - */ - function _multiplyLower($x, $stop) - { - $this_length = count($this->value); - $x_length = count($x->value); - - if ( !$this_length || !$x_length ) { // a 0 is being multiplied - return new Math_BigInteger(); - } - - if ( $this_length < $x_length ) { - return $x->_multiplyLower($this, $stop); - } - - $product = new Math_BigInteger(); - $product->value = $this->_array_repeat(0, $this_length + $x_length); - - // the following for loop could be removed if the for loop following it - // (the one with nested for loops) initially set $i to 0, but - // doing so would also make the result in one set of unnecessary adds, - // since on the outermost loops first pass, $product->value[$k] is going - // to always be 0 - - $carry = 0; - - for ($j = 0; $j < $this_length; $j++) { // ie. $i = 0, $k = $i - $temp = $this->value[$j] * $x->value[0] + $carry; // $product->value[$k] == 0 - $carry = floor($temp / 0x4000000); - $product->value[$j] = $temp - 0x4000000 * $carry; - } - - if ($j < $stop) { - $product->value[$j] = $carry; - } - - // the above for loop is what the previous comment was talking about. the - // following for loop is the "one with nested for loops" - - for ($i = 1; $i < $x_length; $i++) { - $carry = 0; - - for ($j = 0, $k = $i; $j < $this_length && $k < $stop; $j++, $k++) { - $temp = $product->value[$k] + $this->value[$j] * $x->value[$i] + $carry; - $carry = floor($temp / 0x4000000); - $product->value[$k] = $temp - 0x4000000 * $carry; - } - - if ($k < $stop) { - $product->value[$k] = $carry; - } - } - - $product->is_negative = $this->is_negative != $x->is_negative; - - return $product; - } - - /** - * Performs long multiplication on two BigIntegers - * - * Modeled after 'multiply' in MutableBigInteger.java. - * - * @param Math_BigInteger $x - * @return Math_BigInteger - * @access private - */ - function _multiply($x) - { - $this_length = count($this->value); - $x_length = count($x->value); - - if ( !$this_length || !$x_length ) { // a 0 is being multiplied - return new Math_BigInteger(); - } - - if ( $this_length < $x_length ) { - return $x->_multiply($this); - } - - $product = new Math_BigInteger(); - $product->value = $this->_array_repeat(0, $this_length + $x_length); - - // the following for loop could be removed if the for loop following it - // (the one with nested for loops) initially set $i to 0, but - // doing so would also make the result in one set of unnecessary adds, - // since on the outermost loops first pass, $product->value[$k] is going - // to always be 0 - - $carry = 0; - - for ($j = 0; $j < $this_length; $j++) { // ie. $i = 0 - $temp = $this->value[$j] * $x->value[0] + $carry; // $product->value[$k] == 0 - $carry = floor($temp / 0x4000000); - $product->value[$j] = $temp - 0x4000000 * $carry; - } - - $product->value[$j] = $carry; - - // the above for loop is what the previous comment was talking about. the - // following for loop is the "one with nested for loops" - for ($i = 1; $i < $x_length; $i++) { - $carry = 0; - - for ($j = 0, $k = $i; $j < $this_length; $j++, $k++) { - $temp = $product->value[$k] + $this->value[$j] * $x->value[$i] + $carry; - $carry = floor($temp / 0x4000000); - $product->value[$k] = $temp - 0x4000000 * $carry; - } - - $product->value[$k] = $carry; - } - - $product->is_negative = $this->is_negative != $x->is_negative; - - return $this->_normalize($product); - } - - /** - * Performs Karatsuba multiplication on two BigIntegers - * - * See {@link http://en.wikipedia.org/wiki/Karatsuba_algorithm Karatsuba algorithm} and - * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=120 MPM 5.2.3}. - * - * @param Math_BigInteger $y - * @return Math_BigInteger - * @access private - */ - function _karatsuba($y) - { - $x = $this->copy(); - - $m = min(count($x->value) >> 1, count($y->value) >> 1); - - if ($m < MATH_BIGINTEGER_KARATSUBA_CUTOFF) { - return $x->_multiply($y); - } - - $x1 = new Math_BigInteger(); - $x0 = new Math_BigInteger(); - $y1 = new Math_BigInteger(); - $y0 = new Math_BigInteger(); - - $x1->value = array_slice($x->value, $m); - $x0->value = array_slice($x->value, 0, $m); - $y1->value = array_slice($y->value, $m); - $y0->value = array_slice($y->value, 0, $m); - - $z2 = $x1->_karatsuba($y1); - $z0 = $x0->_karatsuba($y0); - - $z1 = $x1->add($x0); - $z1 = $z1->_karatsuba($y1->add($y0)); - $z1 = $z1->subtract($z2->add($z0)); - - $z2->value = array_merge(array_fill(0, 2 * $m, 0), $z2->value); - $z1->value = array_merge(array_fill(0, $m, 0), $z1->value); - - $xy = $z2->add($z1); - $xy = $xy->add($z0); - - return $xy; - } - - /** - * Squares a BigInteger - * - * @return Math_BigInteger - * @access private - */ - function _square() - { - static $cutoff = false; - if ($cutoff === false) { - $cutoff = 2 * MATH_BIGINTEGER_KARATSUBA_CUTOFF; - } - - return count($this->value) < $cutoff ? $this->_baseSquare() : $this->_karatsubaSquare(); - } - - /** - * Performs traditional squaring on two BigIntegers - * - * Squaring can be done faster than multiplying a number by itself can be. See - * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=7 HAC 14.2.4} / - * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=141 MPM 5.3} for more information. - * - * @return Math_BigInteger - * @access private - */ - function _baseSquare() - { - if ( empty($this->value) ) { - return new Math_BigInteger(); - } - - $square = new Math_BigInteger(); - $square->value = $this->_array_repeat(0, 2 * count($this->value)); - - for ($i = 0, $max_index = count($this->value) - 1; $i <= $max_index; $i++) { - $i2 = 2 * $i; - - $temp = $square->value[$i2] + $this->value[$i] * $this->value[$i]; - $carry = floor($temp / 0x4000000); - $square->value[$i2] = $temp - 0x4000000 * $carry; - - // note how we start from $i+1 instead of 0 as we do in multiplication. - for ($j = $i + 1, $k = $i2 + 1; $j <= $max_index; $j++, $k++) { - $temp = $square->value[$k] + 2 * $this->value[$j] * $this->value[$i] + $carry; - $carry = floor($temp / 0x4000000); - $square->value[$k] = $temp - 0x4000000 * $carry; - } - - // the following line can yield values larger 2**15. at this point, PHP should switch - // over to floats. - $square->value[$i + $max_index + 1] = $carry; - } - - return $square; - } - - /** - * Performs Karatsuba "squaring" on two BigIntegers - * - * See {@link http://en.wikipedia.org/wiki/Karatsuba_algorithm Karatsuba algorithm} and - * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=151 MPM 5.3.4}. - * - * @param Math_BigInteger $y - * @return Math_BigInteger - * @access private - */ - function _karatsubaSquare() - { - $m = count($this->value) >> 1; - - if ($m < MATH_BIGINTEGER_KARATSUBA_CUTOFF) { - return $this->_square(); - } - - $x1 = new Math_BigInteger(); - $x0 = new Math_BigInteger(); - - $x1->value = array_slice($this->value, $m); - $x0->value = array_slice($this->value, 0, $m); - - $z2 = $x1->_karatsubaSquare(); - $z0 = $x0->_karatsubaSquare(); - - $z1 = $x1->add($x0); - $z1 = $z1->_karatsubaSquare(); - $z1 = $z1->subtract($z2->add($z0)); - - $z2->value = array_merge(array_fill(0, 2 * $m, 0), $z2->value); - $z1->value = array_merge(array_fill(0, $m, 0), $z1->value); - - $xx = $z2->add($z1); - $xx = $xx->add($z0); - - return $xx; - } - - /** - * Divides two BigIntegers. - * - * Returns an array whose first element contains the quotient and whose second element contains the - * "common residue". If the remainder would be positive, the "common residue" and the remainder are the - * same. If the remainder would be negative, the "common residue" is equal to the sum of the remainder - * and the divisor (basically, the "common residue" is the first positive modulo). - * - * Here's an example: - * - * divide($b); - * - * echo $quotient->toString(); // outputs 0 - * echo "\r\n"; - * echo $remainder->toString(); // outputs 10 - * ?> - * - * - * @param Math_BigInteger $y - * @return Array - * @access public - * @internal This function is based off of {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=9 HAC 14.20}. - */ - function divide($y) - { - switch ( MATH_BIGINTEGER_MODE ) { - case MATH_BIGINTEGER_MODE_GMP: - $quotient = new Math_BigInteger(); - $remainder = new Math_BigInteger(); - - list($quotient->value, $remainder->value) = gmp_div_qr($this->value, $y->value); - - if (gmp_sign($remainder->value) < 0) { - $remainder->value = gmp_add($remainder->value, gmp_abs($y->value)); - } - - return array($this->_normalize($quotient), $this->_normalize($remainder)); - case MATH_BIGINTEGER_MODE_BCMATH: - $quotient = new Math_BigInteger(); - $remainder = new Math_BigInteger(); - - $quotient->value = bcdiv($this->value, $y->value); - $remainder->value = bcmod($this->value, $y->value); - - if ($remainder->value[0] == '-') { - $remainder->value = bcadd($remainder->value, $y->value[0] == '-' ? substr($y->value, 1) : $y->value); - } - - return array($this->_normalize($quotient), $this->_normalize($remainder)); - } - - if (count($y->value) == 1) { - $temp = $this->_divide_digit($y->value[0]); - $temp[0]->is_negative = $this->is_negative != $y->is_negative; - return array($this->_normalize($temp[0]), $this->_normalize($temp[1])); - } - - static $zero; - if (!isset($zero)) { - $zero = new Math_BigInteger(); - } - - $x = $this->copy(); - $y = $y->copy(); - - $x_sign = $x->is_negative; - $y_sign = $y->is_negative; - - $x->is_negative = $y->is_negative = false; - - $diff = $x->compare($y); - - if ( !$diff ) { - $temp = new Math_BigInteger(); - $temp->value = array(1); - $temp->is_negative = $x_sign != $y_sign; - return array($this->_normalize($temp), $this->_normalize(new Math_BigInteger())); - } - - if ( $diff < 0 ) { - // if $x is negative, "add" $y. - if ( $x_sign ) { - $x = $y->subtract($x); - } - return array($this->_normalize(new Math_BigInteger()), $this->_normalize($x)); - } - - // normalize $x and $y as described in HAC 14.23 / 14.24 - $msb = $y->value[count($y->value) - 1]; - for ($shift = 0; !($msb & 0x2000000); $shift++) { - $msb <<= 1; - } - $x->_lshift($shift); - $y->_lshift($shift); - - $x_max = count($x->value) - 1; - $y_max = count($y->value) - 1; - - $quotient = new Math_BigInteger(); - $quotient->value = $this->_array_repeat(0, $x_max - $y_max + 1); - - // $temp = $y << ($x_max - $y_max-1) in base 2**26 - $temp = new Math_BigInteger(); - $temp->value = array_merge($this->_array_repeat(0, $x_max - $y_max), $y->value); - - while ( $x->compare($temp) >= 0 ) { - // calculate the "common residue" - $quotient->value[$x_max - $y_max]++; - $x = $x->subtract($temp); - $x_max = count($x->value) - 1; - } - - for ($i = $x_max; $i >= $y_max + 1; $i--) { - $x_value = array( - $x->value[$i], - ( $i > 0 ) ? $x->value[$i - 1] : 0, - ( $i > 1 ) ? $x->value[$i - 2] : 0 - ); - $y_value = array( - $y->value[$y_max], - ( $y_max > 0 ) ? $y->value[$y_max - 1] : 0 - ); - - $q_index = $i - $y_max - 1; - if ($x_value[0] == $y_value[0]) { - $quotient->value[$q_index] = 0x3FFFFFF; - } else { - $quotient->value[$q_index] = floor( - ($x_value[0] * 0x4000000 + $x_value[1]) - / - $y_value[0] - ); - } - - $temp = new Math_BigInteger(); - $temp->value = array($y_value[1], $y_value[0]); - - $lhs = new Math_BigInteger(); - $lhs->value = array($quotient->value[$q_index]); - $lhs = $lhs->multiply($temp); - - $rhs = new Math_BigInteger(); - $rhs->value = array($x_value[2], $x_value[1], $x_value[0]); - - while ( $lhs->compare($rhs) > 0 ) { - $quotient->value[$q_index]--; - - $lhs = new Math_BigInteger(); - $lhs->value = array($quotient->value[$q_index]); - $lhs = $lhs->multiply($temp); - } - - $adjust = $this->_array_repeat(0, $q_index); - $temp = new Math_BigInteger(); - $temp->value = array($quotient->value[$q_index]); - $temp = $temp->multiply($y); - $temp->value = array_merge($adjust, $temp->value); - - $x = $x->subtract($temp); - - if ($x->compare($zero) < 0) { - $temp->value = array_merge($adjust, $y->value); - $x = $x->add($temp); - - $quotient->value[$q_index]--; - } - - $x_max = count($x->value) - 1; - } - - // unnormalize the remainder - $x->_rshift($shift); - - $quotient->is_negative = $x_sign != $y_sign; - - // calculate the "common residue", if appropriate - if ( $x_sign ) { - $y->_rshift($shift); - $x = $y->subtract($x); - } - - return array($this->_normalize($quotient), $this->_normalize($x)); - } - - /** - * Divides a BigInteger by a regular integer - * - * abc / x = a00 / x + b0 / x + c / x - * - * @param Math_BigInteger $divisor - * @return Array - * @access public - */ - function _divide_digit($divisor) - { - $carry = 0; - $result = new Math_BigInteger(); - - for ($i = count($this->value) - 1; $i >= 0; $i--) { - $temp = 0x4000000 * $carry + $this->value[$i]; - $result->value[$i] = floor($temp / $divisor); - $carry = fmod($temp, $divisor); - } - - $remainder = new Math_BigInteger(); - $remainder->value = array($carry); - - return array($result, $remainder); - } - - /** - * Performs modular exponentiation. - * - * Here's an example: - * - * modPow($b, $c); - * - * echo $c->toString(); // outputs 10 - * ?> - * - * - * @param Math_BigInteger $e - * @param Math_BigInteger $n - * @return Math_BigInteger - * @access public - * @internal The most naive approach to modular exponentiation has very unreasonable requirements, and - * and although the approach involving repeated squaring does vastly better, it, too, is impractical - * for our purposes. The reason being that division - by far the most complicated and time-consuming - * of the basic operations (eg. +,-,*,/) - occurs multiple times within it. - * - * Modular reductions resolve this issue. Although an individual modular reduction takes more time - * then an individual division, when performed in succession (with the same modulo), they're a lot faster. - * - * The two most commonly used modular reductions are Barrett and Montgomery reduction. Montgomery reduction, - * although faster, only works when the gcd of the modulo and of the base being used is 1. In RSA, when the - * base is a power of two, the modulo - a product of two primes - is always going to have a gcd of 1 (because - * the product of two odd numbers is odd), but what about when RSA isn't used? - * - * In contrast, Barrett reduction has no such constraint. As such, some bigint implementations perform a - * Barrett reduction after every operation in the modpow function. Others perform Barrett reductions when the - * modulo is even and Montgomery reductions when the modulo is odd. BigInteger.java's modPow method, however, - * uses a trick involving the Chinese Remainder Theorem to factor the even modulo into two numbers - one odd and - * the other, a power of two - and recombine them, later. This is the method that this modPow function uses. - * {@link http://islab.oregonstate.edu/papers/j34monex.pdf Montgomery Reduction with Even Modulus} elaborates. - */ - function modPow($e, $n) - { - $n = $this->bitmask !== false && $this->bitmask->compare($n) < 0 ? $this->bitmask : $n->abs(); - - if ($e->compare(new Math_BigInteger()) < 0) { - $e = $e->abs(); - - $temp = $this->modInverse($n); - if ($temp === false) { - return false; - } - - return $this->_normalize($temp->modPow($e, $n)); - } - - switch ( MATH_BIGINTEGER_MODE ) { - case MATH_BIGINTEGER_MODE_GMP: - $temp = new Math_BigInteger(); - $temp->value = gmp_powm($this->value, $e->value, $n->value); - - return $this->_normalize($temp); - case MATH_BIGINTEGER_MODE_BCMATH: - $temp = new Math_BigInteger(); - $temp->value = bcpowmod($this->value, $e->value, $n->value); - - return $this->_normalize($temp); - } - - if ( empty($e->value) ) { - $temp = new Math_BigInteger(); - $temp->value = array(1); - return $this->_normalize($temp); - } - - if ( $e->value == array(1) ) { - list(, $temp) = $this->divide($n); - return $this->_normalize($temp); - } - - if ( $e->value == array(2) ) { - $temp = $this->_square(); - list(, $temp) = $temp->divide($n); - return $this->_normalize($temp); - } - - return $this->_normalize($this->_slidingWindow($e, $n, MATH_BIGINTEGER_BARRETT)); - - // is the modulo odd? - if ( $n->value[0] & 1 ) { - return $this->_normalize($this->_slidingWindow($e, $n, MATH_BIGINTEGER_MONTGOMERY)); - } - // if it's not, it's even - - // find the lowest set bit (eg. the max pow of 2 that divides $n) - for ($i = 0; $i < count($n->value); $i++) { - if ( $n->value[$i] ) { - $temp = decbin($n->value[$i]); - $j = strlen($temp) - strrpos($temp, '1') - 1; - $j+= 26 * $i; - break; - } - } - // at this point, 2^$j * $n/(2^$j) == $n - - $mod1 = $n->copy(); - $mod1->_rshift($j); - $mod2 = new Math_BigInteger(); - $mod2->value = array(1); - $mod2->_lshift($j); - - $part1 = ( $mod1->value != array(1) ) ? $this->_slidingWindow($e, $mod1, MATH_BIGINTEGER_MONTGOMERY) : new Math_BigInteger(); - $part2 = $this->_slidingWindow($e, $mod2, MATH_BIGINTEGER_POWEROF2); - - $y1 = $mod2->modInverse($mod1); - $y2 = $mod1->modInverse($mod2); - - $result = $part1->multiply($mod2); - $result = $result->multiply($y1); - - $temp = $part2->multiply($mod1); - $temp = $temp->multiply($y2); - - $result = $result->add($temp); - list(, $result) = $result->divide($n); - - return $this->_normalize($result); - } - - /** - * Performs modular exponentiation. - * - * Alias for Math_BigInteger::modPow() - * - * @param Math_BigInteger $e - * @param Math_BigInteger $n - * @return Math_BigInteger - * @access public - */ - function powMod($e, $n) - { - return $this->modPow($e, $n); - } - - /** - * Sliding Window k-ary Modular Exponentiation - * - * Based on {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=27 HAC 14.85} / - * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=210 MPM 7.7}. In a departure from those algorithims, - * however, this function performs a modular reduction after every multiplication and squaring operation. - * As such, this function has the same preconditions that the reductions being used do. - * - * @param Math_BigInteger $e - * @param Math_BigInteger $n - * @param Integer $mode - * @return Math_BigInteger - * @access private - */ - function _slidingWindow($e, $n, $mode) - { - static $window_ranges = array(7, 25, 81, 241, 673, 1793); // from BigInteger.java's oddModPow function - //static $window_ranges = array(0, 7, 36, 140, 450, 1303, 3529); // from MPM 7.3.1 - - $e_length = count($e->value) - 1; - $e_bits = decbin($e->value[$e_length]); - for ($i = $e_length - 1; $i >= 0; $i--) { - $e_bits.= str_pad(decbin($e->value[$i]), 26, '0', STR_PAD_LEFT); - } - - $e_length = strlen($e_bits); - - // calculate the appropriate window size. - // $window_size == 3 if $window_ranges is between 25 and 81, for example. - for ($i = 0, $window_size = 1; $e_length > $window_ranges[$i] && $i < count($window_ranges); $window_size++, $i++); - switch ($mode) { - case MATH_BIGINTEGER_MONTGOMERY: - $reduce = '_montgomery'; - $prep = '_prepMontgomery'; - break; - case MATH_BIGINTEGER_BARRETT: - $reduce = '_barrett'; - $prep = '_barrett'; - break; - case MATH_BIGINTEGER_POWEROF2: - $reduce = '_mod2'; - $prep = '_mod2'; - break; - case MATH_BIGINTEGER_CLASSIC: - $reduce = '_remainder'; - $prep = '_remainder'; - break; - case MATH_BIGINTEGER_NONE: - // ie. do no modular reduction. useful if you want to just do pow as opposed to modPow. - $reduce = 'copy'; - $prep = 'copy'; - break; - default: - // an invalid $mode was provided - } - - // precompute $this^0 through $this^$window_size - $powers = array(); - $powers[1] = $this->$prep($n); - $powers[2] = $powers[1]->_square(); - $powers[2] = $powers[2]->$reduce($n); - - // we do every other number since substr($e_bits, $i, $j+1) (see below) is supposed to end - // in a 1. ie. it's supposed to be odd. - $temp = 1 << ($window_size - 1); - for ($i = 1; $i < $temp; $i++) { - $powers[2 * $i + 1] = $powers[2 * $i - 1]->multiply($powers[2]); - $powers[2 * $i + 1] = $powers[2 * $i + 1]->$reduce($n); - } - - $result = new Math_BigInteger(); - $result->value = array(1); - $result = $result->$prep($n); - - for ($i = 0; $i < $e_length; ) { - if ( !$e_bits[$i] ) { - $result = $result->_square(); - $result = $result->$reduce($n); - $i++; - } else { - for ($j = $window_size - 1; $j > 0; $j--) { - if ( !empty($e_bits[$i + $j]) ) { - break; - } - } - - for ($k = 0; $k <= $j; $k++) {// eg. the length of substr($e_bits, $i, $j+1) - $result = $result->_square(); - $result = $result->$reduce($n); - } - - $result = $result->multiply($powers[bindec(substr($e_bits, $i, $j + 1))]); - $result = $result->$reduce($n); - - $i+=$j + 1; - } - } - - $result = $result->$reduce($n); - - return $result; - } - - /** - * Remainder - * - * A wrapper for the divide function. - * - * @see divide() - * @see _slidingWindow() - * @access private - * @param Math_BigInteger - * @return Math_BigInteger - */ - function _remainder($n) - { - list(, $temp) = $this->divide($n); - return $temp; - } - - /** - * Modulos for Powers of Two - * - * Calculates $x%$n, where $n = 2**$e, for some $e. Since this is basically the same as doing $x & ($n-1), - * we'll just use this function as a wrapper for doing that. - * - * @see _slidingWindow() - * @access private - * @param Math_BigInteger - * @return Math_BigInteger - */ - function _mod2($n) - { - $temp = new Math_BigInteger(); - $temp->value = array(1); - return $this->bitwise_and($n->subtract($temp)); - } - - /** - * Barrett Modular Reduction - * - * See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=14 HAC 14.3.3} / - * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=165 MPM 6.2.5} for more information. Modified slightly, - * so as not to require negative numbers (initially, this script didn't support negative numbers). - * - * @see _slidingWindow() - * @access private - * @param Math_BigInteger - * @return Math_BigInteger - */ - function _barrett($n) - { - static $cache = array( - MATH_BIGINTEGER_VARIABLE => array(), - MATH_BIGINTEGER_DATA => array() - ); - - $n_length = count($n->value); - - if (count($this->value) > 2 * $n_length) { - list(, $temp) = $this->divide($n); - return $temp; - } - - if ( ($key = array_search($n->value, $cache[MATH_BIGINTEGER_VARIABLE])) === false ) { - $key = count($cache[MATH_BIGINTEGER_VARIABLE]); - $cache[MATH_BIGINTEGER_VARIABLE][] = $n->value; - $temp = new Math_BigInteger(); - $temp->value = $this->_array_repeat(0, 2 * $n_length); - $temp->value[] = 1; - list($cache[MATH_BIGINTEGER_DATA][], ) = $temp->divide($n); - } - - $temp = new Math_BigInteger(); - $temp->value = array_slice($this->value, $n_length - 1); - $temp = $temp->multiply($cache[MATH_BIGINTEGER_DATA][$key]); - $temp->value = array_slice($temp->value, $n_length + 1); - - $result = new Math_BigInteger(); - $result->value = array_slice($this->value, 0, $n_length + 1); - $temp = $temp->_multiplyLower($n, $n_length + 1); - // $temp->value == array_slice($temp->multiply($n)->value, 0, $n_length + 1) - - if ($result->compare($temp) < 0) { - $corrector = new Math_BigInteger(); - $corrector->value = $this->_array_repeat(0, $n_length + 1); - $corrector->value[] = 1; - $result = $result->add($corrector); - } - - $result = $result->subtract($temp); - while ($result->compare($n) > 0) { - $result = $result->subtract($n); - } - - return $result; - } - - /** - * Montgomery Modular Reduction - * - * ($this->_prepMontgomery($n))->_montgomery($n) yields $x%$n. - * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=170 MPM 6.3} provides insights on how this can be - * improved upon (basically, by using the comba method). gcd($n, 2) must be equal to one for this function - * to work correctly. - * - * @see _prepMontgomery() - * @see _slidingWindow() - * @access private - * @param Math_BigInteger - * @return Math_BigInteger - */ - function _montgomery($n) - { - static $cache = array( - MATH_BIGINTEGER_VARIABLE => array(), - MATH_BIGINTEGER_DATA => array() - ); - - if ( ($key = array_search($n->value, $cache[MATH_BIGINTEGER_VARIABLE])) === false ) { - $key = count($cache[MATH_BIGINTEGER_VARIABLE]); - $cache[MATH_BIGINTEGER_VARIABLE][] = $n->value; - $cache[MATH_BIGINTEGER_DATA][] = $n->_modInverse67108864(); - } - - $k = count($n->value); - - $result = $this->copy(); - - for ($i = 0; $i < $k; $i++) { - $temp = new Math_BigInteger(); - $temp->value = array( - ($result->value[$i] * $cache[MATH_BIGINTEGER_DATA][$key]) & 0x3FFFFFF - ); - - $temp = $temp->multiply($n); - $temp->value = array_merge($this->_array_repeat(0, $i), $temp->value); - $result = $result->add($temp); - } - - $result->value = array_slice($result->value, $k); - - if ($result->compare($n) >= 0) { - $result = $result->subtract($n); - } - - return $result; - } - - /** - * Prepare a number for use in Montgomery Modular Reductions - * - * @see _montgomery() - * @see _slidingWindow() - * @access private - * @param Math_BigInteger - * @return Math_BigInteger - */ - function _prepMontgomery($n) - { - $k = count($n->value); - - $temp = new Math_BigInteger(); - $temp->value = array_merge($this->_array_repeat(0, $k), $this->value); - - list(, $temp) = $temp->divide($n); - return $temp; - } - - /** - * Modular Inverse of a number mod 2**26 (eg. 67108864) - * - * Based off of the bnpInvDigit function implemented and justified in the following URL: - * - * {@link http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn.js} - * - * The following URL provides more info: - * - * {@link http://groups.google.com/group/sci.crypt/msg/7a137205c1be7d85} - * - * As for why we do all the bitmasking... strange things can happen when converting from floats to ints. For - * instance, on some computers, var_dump((int) -4294967297) yields int(-1) and on others, it yields - * int(-2147483648). To avoid problems stemming from this, we use bitmasks to guarantee that ints aren't - * auto-converted to floats. The outermost bitmask is present because without it, there's no guarantee that - * the "residue" returned would be the so-called "common residue". We use fmod, in the last step, because the - * maximum possible $x is 26 bits and the maximum $result is 16 bits. Thus, we have to be able to handle up to - * 40 bits, which only 64-bit floating points will support. - * - * Thanks to Pedro Gimeno Fortea for input! - * - * @see _montgomery() - * @access private - * @return Integer - */ - function _modInverse67108864() // 2**26 == 67108864 - { - $x = -$this->value[0]; - $result = $x & 0x3; // x**-1 mod 2**2 - $result = ($result * (2 - $x * $result)) & 0xF; // x**-1 mod 2**4 - $result = ($result * (2 - ($x & 0xFF) * $result)) & 0xFF; // x**-1 mod 2**8 - $result = ($result * ((2 - ($x & 0xFFFF) * $result) & 0xFFFF)) & 0xFFFF; // x**-1 mod 2**16 - $result = fmod($result * (2 - fmod($x * $result, 0x4000000)), 0x4000000); // x**-1 mod 2**26 - return $result & 0x3FFFFFF; - } - - /** - * Calculates modular inverses. - * - * Say you have (30 mod 17 * x mod 17) mod 17 == 1. x can be found using modular inverses. - * - * Here's an example: - * - * modInverse($b); - * echo $c->toString(); // outputs 4 - * - * echo "\r\n"; - * - * $d = $a->multiply($c); - * list(, $d) = $d->divide($b); - * echo $d; // outputs 1 (as per the definition of modular inverse) - * ?> - * - * - * @param Math_BigInteger $n - * @return mixed false, if no modular inverse exists, Math_BigInteger, otherwise. - * @access public - * @internal See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=21 HAC 14.64} for more information. - */ - function modInverse($n) - { - switch ( MATH_BIGINTEGER_MODE ) { - case MATH_BIGINTEGER_MODE_GMP: - $temp = new Math_BigInteger(); - $temp->value = gmp_invert($this->value, $n->value); - - return ( $temp->value === false ) ? false : $this->_normalize($temp); - } - - static $zero, $one; - if (!isset($zero)) { - $zero = new Math_BigInteger(); - $one = new Math_BigInteger(1); - } - - // $x mod $n == $x mod -$n. - $n = $n->abs(); - - if ($this->compare($zero) < 0) { - $temp = $this->abs(); - $temp = $temp->modInverse($n); - return $negated === false ? false : $this->_normalize($n->subtract($temp)); - } - - extract($this->extendedGCD($n)); - - if (!$gcd->equals($one)) { - return false; - } - - $x = $x->compare($zero) < 0 ? $x->add($n) : $x; - - return $this->compare($zero) < 0 ? $this->_normalize($n->subtract($x)) : $this->_normalize($x); - } - - /** - * Calculates the greatest common divisor and Bézout's identity. - * - * Say you have 693 and 609. The GCD is 21. Bézout's identity states that there exist integers x and y such that - * 693*x + 609*y == 21. In point of fact, there are actually an infinite number of x and y combinations and which - * combination is returned is dependant upon which mode is in use. See - * {@link http://en.wikipedia.org/wiki/B%C3%A9zout%27s_identity Bézout's identity - Wikipedia} for more information. - * - * Here's an example: - * - * extendedGCD($b)); - * - * echo $gcd->toString() . "\r\n"; // outputs 21 - * echo $a->toString() * $x->toString() + $b->toString() * $y->toString(); // outputs 21 - * ?> - * - * - * @param Math_BigInteger $n - * @return Math_BigInteger - * @access public - * @internal Calculates the GCD using the binary xGCD algorithim described in - * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=19 HAC 14.61}. As the text above 14.61 notes, - * the more traditional algorithim requires "relatively costly multiple-precision divisions". - */ - function extendedGCD($n) { - switch ( MATH_BIGINTEGER_MODE ) { - case MATH_BIGINTEGER_MODE_GMP: - extract(gmp_gcdext($this->value, $n->value)); - - return array( - 'gcd' => $this->_normalize(new Math_BigInteger($g)), - 'x' => $this->_normalize(new Math_BigInteger($s)), - 'y' => $this->_normalize(new Math_BigInteger($t)) - ); - case MATH_BIGINTEGER_MODE_BCMATH: - // it might be faster to use the binary xGCD algorithim here, as well, but (1) that algorithim works - // best when the base is a power of 2 and (2) i don't think it'd make much difference, anyway. as is, - // the basic extended euclidean algorithim is what we're using. - - $u = $this->value; - $v = $n->value; - - $a = '1'; - $b = '0'; - $c = '0'; - $d = '1'; - - while (bccomp($v, '0') != 0) { - $q = bcdiv($u, $v); - - $temp = $u; - $u = $v; - $v = bcsub($temp, bcmul($v, $q)); - - $temp = $a; - $a = $c; - $c = bcsub($temp, bcmul($a, $q)); - - $temp = $b; - $b = $d; - $d = bcsub($temp, bcmul($b, $q)); - } - - return array( - 'gcd' => $this->_normalize(new Math_BigInteger($u)), - 'x' => $this->_normalize(new Math_BigInteger($a)), - 'y' => $this->_normalize(new Math_BigInteger($b)) - ); - } - - $y = $n->copy(); - $x = $this->copy(); - $g = new Math_BigInteger(); - $g->value = array(1); - - while ( !(($x->value[0] & 1)|| ($y->value[0] & 1)) ) { - $x->_rshift(1); - $y->_rshift(1); - $g->_lshift(1); - } - - $u = $x->copy(); - $v = $y->copy(); - - $a = new Math_BigInteger(); - $b = new Math_BigInteger(); - $c = new Math_BigInteger(); - $d = new Math_BigInteger(); - - $a->value = $d->value = $g->value = array(1); - - while ( !empty($u->value) ) { - while ( !($u->value[0] & 1) ) { - $u->_rshift(1); - if ( ($a->value[0] & 1) || ($b->value[0] & 1) ) { - $a = $a->add($y); - $b = $b->subtract($x); - } - $a->_rshift(1); - $b->_rshift(1); - } - - while ( !($v->value[0] & 1) ) { - $v->_rshift(1); - if ( ($c->value[0] & 1) || ($d->value[0] & 1) ) { - $c = $c->add($y); - $d = $d->subtract($x); - } - $c->_rshift(1); - $d->_rshift(1); - } - - if ($u->compare($v) >= 0) { - $u = $u->subtract($v); - $a = $a->subtract($c); - $b = $b->subtract($d); - } else { - $v = $v->subtract($u); - $c = $c->subtract($a); - $d = $d->subtract($b); - } - } - - return array( - 'gcd' => $this->_normalize($g->multiply($v)), - 'x' => $this->_normalize($c), - 'y' => $this->_normalize($d) - ); - } - - /** - * Calculates the greatest common divisor - * - * Say you have 693 and 609. The GCD is 21. - * - * Here's an example: - * - * extendedGCD($b); - * - * echo $gcd->toString() . "\r\n"; // outputs 21 - * ?> - * - * - * @param Math_BigInteger $n - * @return Math_BigInteger - * @access public - */ - function gcd($n) - { - extract($this->extendedGCD($n)); - return $gcd; - } - - /** - * Absolute value. - * - * @return Math_BigInteger - * @access public - */ - function abs() - { - $temp = new Math_BigInteger(); - - switch ( MATH_BIGINTEGER_MODE ) { - case MATH_BIGINTEGER_MODE_GMP: - $temp->value = gmp_abs($this->value); - break; - case MATH_BIGINTEGER_MODE_BCMATH: - $temp->value = (bccomp($this->value, '0') < 0) ? substr($this->value, 1) : $this->value; - break; - default: - $temp->value = $this->value; - } - - return $temp; - } - - /** - * Compares two numbers. - * - * Although one might think !$x->compare($y) means $x != $y, it, in fact, means the opposite. The reason for this is - * demonstrated thusly: - * - * $x > $y: $x->compare($y) > 0 - * $x < $y: $x->compare($y) < 0 - * $x == $y: $x->compare($y) == 0 - * - * Note how the same comparison operator is used. If you want to test for equality, use $x->equals($y). - * - * @param Math_BigInteger $x - * @return Integer < 0 if $this is less than $x; > 0 if $this is greater than $x, and 0 if they are equal. - * @access public - * @see equals() - * @internal Could return $this->sub($x), but that's not as fast as what we do do. - */ - function compare($y) - { - switch ( MATH_BIGINTEGER_MODE ) { - case MATH_BIGINTEGER_MODE_GMP: - return gmp_cmp($this->value, $y->value); - case MATH_BIGINTEGER_MODE_BCMATH: - return bccomp($this->value, $y->value); - } - - $x = $this->_normalize($this->copy()); - $y = $this->_normalize($y); - - if ( $x->is_negative != $y->is_negative ) { - return ( !$x->is_negative && $y->is_negative ) ? 1 : -1; - } - - $result = $x->is_negative ? -1 : 1; - - if ( count($x->value) != count($y->value) ) { - return ( count($x->value) > count($y->value) ) ? $result : -$result; - } - - for ($i = count($x->value) - 1; $i >= 0; $i--) { - if ($x->value[$i] != $y->value[$i]) { - return ( $x->value[$i] > $y->value[$i] ) ? $result : -$result; - } - } - - return 0; - } - - /** - * Tests the equality of two numbers. - * - * If you need to see if one number is greater than or less than another number, use Math_BigInteger::compare() - * - * @param Math_BigInteger $x - * @return Boolean - * @access public - * @see compare() - */ - function equals($x) - { - switch ( MATH_BIGINTEGER_MODE ) { - case MATH_BIGINTEGER_MODE_GMP: - return gmp_cmp($this->value, $x->value) == 0; - default: - return $this->value == $x->value && $this->is_negative == $x->is_negative; - } - } - - /** - * Set Precision - * - * Some bitwise operations give different results depending on the precision being used. Examples include left - * shift, not, and rotates. - * - * @param Math_BigInteger $x - * @access public - * @return Math_BigInteger - */ - function setPrecision($bits) - { - $this->precision = $bits; - if ( MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_BCMATH ) { - $this->bitmask = new Math_BigInteger(chr((1 << ($bits & 0x7)) - 1) . str_repeat(chr(0xFF), $bits >> 3), 256); - } else { - $this->bitmask = new Math_BigInteger(bcpow('2', $bits)); - } - } - - /** - * Logical And - * - * @param Math_BigInteger $x - * @access public - * @internal Implemented per a request by Lluis Pamies i Juarez - * @return Math_BigInteger - */ - function bitwise_and($x) - { - switch ( MATH_BIGINTEGER_MODE ) { - case MATH_BIGINTEGER_MODE_GMP: - $temp = new Math_BigInteger(); - $temp->value = gmp_and($this->value, $x->value); - - return $this->_normalize($temp); - case MATH_BIGINTEGER_MODE_BCMATH: - $left = $this->toBytes(); - $right = $x->toBytes(); - - $length = max(strlen($left), strlen($right)); - - $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); - $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); - - return $this->_normalize(new Math_BigInteger($left & $right, 256)); - } - - $result = $this->copy(); - - $length = min(count($x->value), count($this->value)); - - $result->value = array_slice($result->value, 0, $length); - - for ($i = 0; $i < $length; $i++) { - $result->value[$i] = $result->value[$i] & $x->value[$i]; - } - - return $this->_normalize($result); - } - - /** - * Logical Or - * - * @param Math_BigInteger $x - * @access public - * @internal Implemented per a request by Lluis Pamies i Juarez - * @return Math_BigInteger - */ - function bitwise_or($x) - { - switch ( MATH_BIGINTEGER_MODE ) { - case MATH_BIGINTEGER_MODE_GMP: - $temp = new Math_BigInteger(); - $temp->value = gmp_or($this->value, $x->value); - - return $this->_normalize($temp); - case MATH_BIGINTEGER_MODE_BCMATH: - $left = $this->toBytes(); - $right = $x->toBytes(); - - $length = max(strlen($left), strlen($right)); - - $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); - $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); - - return $this->_normalize(new Math_BigInteger($left | $right, 256)); - } - - $length = max(count($this->value), count($x->value)); - $result = $this->copy(); - $result->value = array_pad($result->value, 0, $length); - $x->value = array_pad($x->value, 0, $length); - - for ($i = 0; $i < $length; $i++) { - $result->value[$i] = $this->value[$i] | $x->value[$i]; - } - - return $this->_normalize($result); - } - - /** - * Logical Exclusive-Or - * - * @param Math_BigInteger $x - * @access public - * @internal Implemented per a request by Lluis Pamies i Juarez - * @return Math_BigInteger - */ - function bitwise_xor($x) - { - switch ( MATH_BIGINTEGER_MODE ) { - case MATH_BIGINTEGER_MODE_GMP: - $temp = new Math_BigInteger(); - $temp->value = gmp_xor($this->value, $x->value); - - return $this->_normalize($temp); - case MATH_BIGINTEGER_MODE_BCMATH: - $left = $this->toBytes(); - $right = $x->toBytes(); - - $length = max(strlen($left), strlen($right)); - - $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); - $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); - - return $this->_normalize(new Math_BigInteger($left ^ $right, 256)); - } - - $length = max(count($this->value), count($x->value)); - $result = $this->copy(); - $result->value = array_pad($result->value, 0, $length); - $x->value = array_pad($x->value, 0, $length); - - for ($i = 0; $i < $length; $i++) { - $result->value[$i] = $this->value[$i] ^ $x->value[$i]; - } - - return $this->_normalize($result); - } - - /** - * Logical Not - * - * @access public - * @internal Implemented per a request by Lluis Pamies i Juarez - * @return Math_BigInteger - */ - function bitwise_not() - { - // calculuate "not" without regard to $this->precision - // (will always result in a smaller number. ie. ~1 isn't 1111 1110 - it's 0) - $temp = $this->toBytes(); - $pre_msb = decbin(ord($temp[0])); - $temp = ~$temp; - $msb = decbin(ord($temp[0])); - if (strlen($msb) == 8) { - $msb = substr($msb, strpos($msb, '0')); - } - $temp[0] = chr(bindec($msb)); - - // see if we need to add extra leading 1's - $current_bits = strlen($pre_msb) + 8 * strlen($temp) - 8; - $new_bits = $this->precision - $current_bits; - if ($new_bits <= 0) { - return $this->_normalize(new Math_BigInteger($temp, 256)); - } - - // generate as many leading 1's as we need to. - $leading_ones = chr((1 << ($new_bits & 0x7)) - 1) . str_repeat(chr(0xFF), $new_bits >> 3); - $this->_base256_lshift($leading_ones, $current_bits); - - $temp = str_pad($temp, ceil($this->bits / 8), chr(0), STR_PAD_LEFT); - - return $this->_normalize(new Math_BigInteger($leading_ones | $temp, 256)); - } - - /** - * Logical Right Shift - * - * Shifts BigInteger's by $shift bits, effectively dividing by 2**$shift. - * - * @param Integer $shift - * @return Math_BigInteger - * @access public - * @internal The only version that yields any speed increases is the internal version. - */ - function bitwise_rightShift($shift) - { - $temp = new Math_BigInteger(); - - switch ( MATH_BIGINTEGER_MODE ) { - case MATH_BIGINTEGER_MODE_GMP: - static $two; - - if (empty($two)) { - $two = gmp_init('2'); - } - - $temp->value = gmp_div_q($this->value, gmp_pow($two, $shift)); - - break; - case MATH_BIGINTEGER_MODE_BCMATH: - $temp->value = bcdiv($this->value, bcpow('2', $shift)); - - break; - default: // could just replace _lshift with this, but then all _lshift() calls would need to be rewritten - // and I don't want to do that... - $temp->value = $this->value; - $temp->_rshift($shift); - } - - return $this->_normalize($temp); - } - - /** - * Logical Left Shift - * - * Shifts BigInteger's by $shift bits, effectively multiplying by 2**$shift. - * - * @param Integer $shift - * @return Math_BigInteger - * @access public - * @internal The only version that yields any speed increases is the internal version. - */ - function bitwise_leftShift($shift) - { - $temp = new Math_BigInteger(); - - switch ( MATH_BIGINTEGER_MODE ) { - case MATH_BIGINTEGER_MODE_GMP: - static $two; - - if (empty($two)) { - $two = gmp_init('2'); - } - - $temp->value = gmp_mul($this->value, gmp_pow($two, $shift)); - - break; - case MATH_BIGINTEGER_MODE_BCMATH: - $temp->value = bcmul($this->value, bcpow('2', $shift)); - - break; - default: // could just replace _rshift with this, but then all _lshift() calls would need to be rewritten - // and I don't want to do that... - $temp->value = $this->value; - $temp->_lshift($shift); - } - - return $this->_normalize($temp); - } - - /** - * Logical Left Rotate - * - * Instead of the top x bits being dropped they're appended to the shifted bit string. - * - * @param Integer $shift - * @return Math_BigInteger - * @access public - */ - function bitwise_leftRotate($shift) - { - $bits = $this->toBytes(); - - if ($this->precision > 0) { - $precision = $this->precision; - if ( MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_BCMATH ) { - $mask = $this->bitmask->subtract(new Math_BigInteger(1)); - $mask = $mask->toBytes(); - } else { - $mask = $this->bitmask->toBytes(); - } - } else { - $temp = ord($bits[0]); - for ($i = 0; $temp >> $i; $i++); - $precision = 8 * strlen($bits) - 8 + $i; - $mask = chr((1 << ($precision & 0x7)) - 1) . str_repeat(chr(0xFF), $precision >> 3); - } - - if ($shift < 0) { - $shift+= $precision; - } - $shift%= $precision; - - if (!$shift) { - return $this->copy(); - } - - $left = $this->bitwise_leftShift($shift); - $left = $left->bitwise_and(new Math_BigInteger($mask, 256)); - $right = $this->bitwise_rightShift($precision - $shift); - $result = MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_BCMATH ? $left->bitwise_or($right) : $left->add($right); - return $this->_normalize($result); - } - - /** - * Logical Right Rotate - * - * Instead of the bottom x bits being dropped they're prepended to the shifted bit string. - * - * @param Integer $shift - * @return Math_BigInteger - * @access public - */ - function bitwise_rightRotate($shift) - { - return $this->bitwise_leftRotate(-$shift); - } - - /** - * Set random number generator function - * - * $generator should be the name of a random generating function whose first parameter is the minimum - * value and whose second parameter is the maximum value. If this function needs to be seeded, it should - * be seeded prior to calling Math_BigInteger::random() or Math_BigInteger::randomPrime() - * - * If the random generating function is not explicitly set, it'll be assumed to be mt_rand(). - * - * @see random() - * @see randomPrime() - * @param optional String $generator - * @access public - */ - function setRandomGenerator($generator) - { - $this->generator = $generator; - } - - /** - * Generate a random number - * - * @param optional Integer $min - * @param optional Integer $max - * @return Math_BigInteger - * @access public - */ - function random($min = false, $max = false) - { - if ($min === false) { - $min = new Math_BigInteger(0); - } - - if ($max === false) { - $max = new Math_BigInteger(0x7FFFFFFF); - } - - $compare = $max->compare($min); - - if (!$compare) { - return $this->_normalize($min); - } else if ($compare < 0) { - // if $min is bigger then $max, swap $min and $max - $temp = $max; - $max = $min; - $min = $temp; - } - - $generator = $this->generator; - - $max = $max->subtract($min); - $max = ltrim($max->toBytes(), chr(0)); - $size = strlen($max) - 1; - $random = ''; - - $bytes = $size & 1; - for ($i = 0; $i < $bytes; $i++) { - $random.= chr($generator(0, 255)); - } - - $blocks = $size >> 1; - for ($i = 0; $i < $blocks; $i++) { - // mt_rand(-2147483648, 0x7FFFFFFF) always produces -2147483648 on some systems - $random.= pack('n', $generator(0, 0xFFFF)); - } - - $temp = new Math_BigInteger($random, 256); - if ($temp->compare(new Math_BigInteger(substr($max, 1), 256)) > 0) { - $random = chr($generator(0, ord($max[0]) - 1)) . $random; - } else { - $random = chr($generator(0, ord($max[0]) )) . $random; - } - - $random = new Math_BigInteger($random, 256); - - return $this->_normalize($random->add($min)); - } - - /** - * Generate a random prime number. - * - * If there's not a prime within the given range, false will be returned. If more than $timeout seconds have elapsed, - * give up and return false. - * - * @param optional Integer $min - * @param optional Integer $max - * @param optional Integer $timeout - * @return Math_BigInteger - * @access public - * @internal See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap4.pdf#page=15 HAC 4.44}. - */ - function randomPrime($min = false, $max = false, $timeout = false) - { - // gmp_nextprime() requires PHP 5 >= 5.2.0 per . - if ( MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_GMP && function_exists('gmp_nextprime') ) { - // we don't rely on Math_BigInteger::random()'s min / max when gmp_nextprime() is being used since this function - // does its own checks on $max / $min when gmp_nextprime() is used. When gmp_nextprime() is not used, however, - // the same $max / $min checks are not performed. - if ($min === false) { - $min = new Math_BigInteger(0); - } - - if ($max === false) { - $max = new Math_BigInteger(0x7FFFFFFF); - } - - $compare = $max->compare($min); - - if (!$compare) { - return $min; - } else if ($compare < 0) { - // if $min is bigger then $max, swap $min and $max - $temp = $max; - $max = $min; - $min = $temp; - } - - $x = $this->random($min, $max); - - $x->value = gmp_nextprime($x->value); - - if ($x->compare($max) <= 0) { - return $x; - } - - $x->value = gmp_nextprime($min->value); - - if ($x->compare($max) <= 0) { - return $x; - } - - return false; - } - - $repeat1 = $repeat2 = array(); - - $one = new Math_BigInteger(1); - $two = new Math_BigInteger(2); - - $start = time(); - - do { - if ($timeout !== false && time() - $start > $timeout) { - return false; - } - - $x = $this->random($min, $max); - if ($x->equals($two)) { - return $x; - } - - // make the number odd - switch ( MATH_BIGINTEGER_MODE ) { - case MATH_BIGINTEGER_MODE_GMP: - gmp_setbit($x->value, 0); - break; - case MATH_BIGINTEGER_MODE_BCMATH: - if ($x->value[strlen($x->value) - 1] % 2 == 0) { - $x = $x->add($one); - } - break; - default: - $x->value[0] |= 1; - } - - // if we've seen this number twice before, assume there are no prime numbers within the given range - if (in_array($x->value, $repeat1)) { - if (in_array($x->value, $repeat2)) { - return false; - } else { - $repeat2[] = $x->value; - } - } else { - $repeat1[] = $x->value; - } - } while (!$x->isPrime()); - - return $x; - } - - /** - * Checks a numer to see if it's prime - * - * Assuming the $t parameter is not set, this functoin has an error rate of 2**-80. The main motivation for the - * $t parameter is distributability. Math_BigInteger::randomPrime() can be distributed accross multiple pageloads - * on a website instead of just one. - * - * @param optional Integer $t - * @return Boolean - * @access public - * @internal Uses the - * {@link http://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test Miller–Rabin primality test}. See - * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap4.pdf#page=8 HAC 4.24}. - */ - function isPrime($t = false) - { - $length = strlen($this->toBytes()); - - if (!$t) { - // see HAC 4.49 "Note (controlling the error probability)" - if ($length >= 163) { $t = 2; } // floor(1300 / 8) - else if ($length >= 106) { $t = 3; } // floor( 850 / 8) - else if ($length >= 81 ) { $t = 4; } // floor( 650 / 8) - else if ($length >= 68 ) { $t = 5; } // floor( 550 / 8) - else if ($length >= 56 ) { $t = 6; } // floor( 450 / 8) - else if ($length >= 50 ) { $t = 7; } // floor( 400 / 8) - else if ($length >= 43 ) { $t = 8; } // floor( 350 / 8) - else if ($length >= 37 ) { $t = 9; } // floor( 300 / 8) - else if ($length >= 31 ) { $t = 12; } // floor( 250 / 8) - else if ($length >= 25 ) { $t = 15; } // floor( 200 / 8) - else if ($length >= 18 ) { $t = 18; } // floor( 150 / 8) - else { $t = 27; } - } - - // ie. gmp_testbit($this, 0) - // ie. isEven() or !isOdd() - switch ( MATH_BIGINTEGER_MODE ) { - case MATH_BIGINTEGER_MODE_GMP: - return gmp_prob_prime($this->value, $t) != 0; - case MATH_BIGINTEGER_MODE_BCMATH: - if ($this->value == '2') { - return true; - } - if ($this->value[strlen($this->value) - 1] % 2 == 0) { - return false; - } - break; - default: - if ($this->value == array(2)) { - return true; - } - if (~$this->value[0] & 1) { - return false; - } - } - - static $primes, $zero, $one, $two; - - if (!isset($primes)) { - $primes = array( - 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, - 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, - 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, - 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, - 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, - 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, - 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, - 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, - 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, - 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, - 953, 967, 971, 977, 983, 991, 997 - ); - - for ($i = 0; $i < count($primes); $i++) { - $primes[$i] = new Math_BigInteger($primes[$i]); - } - - $zero = new Math_BigInteger(); - $one = new Math_BigInteger(1); - $two = new Math_BigInteger(2); - } - - // see HAC 4.4.1 "Random search for probable primes" - for ($i = 0; $i < count($primes); $i++) { - list(, $r) = $this->divide($primes[$i]); - if ($r->equals($zero)) { - return false; - } - } - - $n = $this->copy(); - $n_1 = $n->subtract($one); - $n_2 = $n->subtract($two); - - $r = $n_1->copy(); - // ie. $s = gmp_scan1($n, 0) and $r = gmp_div_q($n, gmp_pow(gmp_init('2'), $s)); - if ( MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_BCMATH ) { - $s = 0; - while ($r->value[strlen($r->value) - 1] % 2 == 0) { - $r->value = bcdiv($r->value, 2); - $s++; - } - } else { - for ($i = 0; $i < count($r->value); $i++) { - $temp = ~$r->value[$i] & 0xFFFFFF; - for ($j = 1; ($temp >> $j) & 1; $j++); - if ($j != 25) { - break; - } - } - $s = 26 * $i + $j - 1; - $r->_rshift($s); - } - - for ($i = 0; $i < $t; $i++) { - $a = new Math_BigInteger(); - $a = $a->random($two, $n_2); - $y = $a->modPow($r, $n); - - if (!$y->equals($one) && !$y->equals($n_1)) { - for ($j = 1; $j < $s && !$y->equals($n_1); $j++) { - $y = $y->modPow($two, $n); - if ($y->equals($one)) { - return false; - } - } - - if (!$y->equals($n_1)) { - return false; - } - } - } - return true; - } - - /** - * Logical Left Shift - * - * Shifts BigInteger's by $shift bits. - * - * @param Integer $shift - * @access private - */ - function _lshift($shift) - { - if ( $shift == 0 ) { - return; - } - - $num_digits = floor($shift / 26); - $shift %= 26; - $shift = 1 << $shift; - - $carry = 0; - - for ($i = 0; $i < count($this->value); $i++) { - $temp = $this->value[$i] * $shift + $carry; - $carry = floor($temp / 0x4000000); - $this->value[$i] = $temp - $carry * 0x4000000; - } - - if ( $carry ) { - $this->value[] = $carry; - } - - while ($num_digits--) { - array_unshift($this->value, 0); - } - } - - /** - * Logical Right Shift - * - * Shifts BigInteger's by $shift bits. - * - * @param Integer $shift - * @access private - */ - function _rshift($shift) - { - if ($shift == 0) { - return; - } - - $num_digits = floor($shift / 26); - $shift %= 26; - $carry_shift = 26 - $shift; - $carry_mask = (1 << $shift) - 1; - - if ( $num_digits ) { - $this->value = array_slice($this->value, $num_digits); - } - - $carry = 0; - - for ($i = count($this->value) - 1; $i >= 0; $i--) { - $temp = $this->value[$i] >> $shift | $carry; - $carry = ($this->value[$i] & $carry_mask) << $carry_shift; - $this->value[$i] = $temp; - } - } - - /** - * Normalize - * - * Deletes leading zeros and truncates (if necessary) to maintain the appropriate precision - * - * @return Math_BigInteger - * @access private - */ - function _normalize($result) - { - $result->precision = $this->precision; - $result->bitmask = $this->bitmask; - - switch ( MATH_BIGINTEGER_MODE ) { - case MATH_BIGINTEGER_MODE_GMP: - if (!empty($result->bitmask->value)) { - $result->value = gmp_and($result->value, $result->bitmask->value); - } - - return $result; - case MATH_BIGINTEGER_MODE_BCMATH: - if (!empty($result->bitmask->value)) { - $result->value = bcmod($result->value, $result->bitmask->value); - } - - return $result; - } - - if ( !count($result->value) ) { - return $result; - } - - for ($i = count($result->value) - 1; $i >= 0; $i--) { - if ( $result->value[$i] ) { - break; - } - unset($result->value[$i]); - } - - if (!empty($result->bitmask->value)) { - $length = min(count($result->value), count($this->bitmask->value)); - $result->value = array_slice($result->value, 0, $length); - - for ($i = 0; $i < $length; $i++) { - $result->value[$i] = $result->value[$i] & $this->bitmask->value[$i]; - } - } - - return $result; - } - - /** - * Array Repeat - * - * @param $input Array - * @param $multiplier mixed - * @return Array - * @access private - */ - function _array_repeat($input, $multiplier) - { - return ($multiplier) ? array_fill(0, $multiplier, $input) : array(); - } - - /** - * Logical Left Shift - * - * Shifts binary strings $shift bits, essentially multiplying by 2**$shift. - * - * @param $x String - * @param $shift Integer - * @return String - * @access private - */ - function _base256_lshift(&$x, $shift) - { - if ($shift == 0) { - return; - } - - $num_bytes = $shift >> 3; // eg. floor($shift/8) - $shift &= 7; // eg. $shift % 8 - - $carry = 0; - for ($i = strlen($x) - 1; $i >= 0; $i--) { - $temp = ord($x[$i]) << $shift | $carry; - $x[$i] = chr($temp); - $carry = $temp >> 8; - } - $carry = ($carry != 0) ? chr($carry) : ''; - $x = $carry . $x . str_repeat(chr(0), $num_bytes); - } - - /** - * Logical Right Shift - * - * Shifts binary strings $shift bits, essentially dividing by 2**$shift and returning the remainder. - * - * @param $x String - * @param $shift Integer - * @return String - * @access private - */ - function _base256_rshift(&$x, $shift) - { - if ($shift == 0) { - $x = ltrim($x, chr(0)); - return ''; - } - - $num_bytes = $shift >> 3; // eg. floor($shift/8) - $shift &= 7; // eg. $shift % 8 - - $remainder = ''; - if ($num_bytes) { - $start = $num_bytes > strlen($x) ? -strlen($x) : -$num_bytes; - $remainder = substr($x, $start); - $x = substr($x, 0, -$num_bytes); - } - - $carry = 0; - $carry_shift = 8 - $shift; - for ($i = 0; $i < strlen($x); $i++) { - $temp = (ord($x[$i]) >> $shift) | $carry; - $carry = (ord($x[$i]) << $carry_shift) & 0xFF; - $x[$i] = chr($temp); - } - $x = ltrim($x, chr(0)); - - $remainder = chr($carry >> $carry_shift) . $remainder; - - return ltrim($remainder, chr(0)); - } - - // one quirk about how the following functions are implemented is that PHP defines N to be an unsigned long - // at 32-bits, while java's longs are 64-bits. - - /** - * Converts 32-bit integers to bytes. - * - * @param Integer $x - * @return String - * @access private - */ - function _int2bytes($x) - { - return ltrim(pack('N', $x), chr(0)); - } - - /** - * Converts bytes to 32-bit integers - * - * @param String $x - * @return Integer - * @access private - */ - function _bytes2int($x) - { - $temp = unpack('Nint', str_pad($x, 4, chr(0), STR_PAD_LEFT)); - return $temp['int']; - } +> and << cannot be used, nor can the modulo operator %, + * which only supports integers. Although this fact will slow this library down, the fact that such a high + * base is being used should more than compensate. + * + * When PHP version 6 is officially released, we'll be able to use 64-bit integers. This should, once again, + * allow bitwise operators, and will increase the maximum possible base to 2**31 (or 2**62 for addition / + * subtraction). + * + * Numbers are stored in {@link http://en.wikipedia.org/wiki/Endianness little endian} format. ie. + * (new Math_BigInteger(pow(2, 26)))->value = array(0, 1) + * + * Useful resources are as follows: + * + * - {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf Handbook of Applied Cryptography (HAC)} + * - {@link http://math.libtomcrypt.com/files/tommath.pdf Multi-Precision Math (MPM)} + * - Java's BigInteger classes. See /j2se/src/share/classes/java/math in jdk-1_5_0-src-jrl.zip + * + * Here's an example of how to use this library: + * + * add($b); + * + * echo $c->toString(); // outputs 5 + * ?> + * + * + * LICENSE: This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * @category Math + * @package Math_BigInteger + * @author Jim Wigginton + * @copyright MMVI Jim Wigginton + * @license http://www.gnu.org/licenses/lgpl.txt + * @version $Id: BigInteger.php,v 1.31 2010/03/01 17:28:19 terrafrost Exp $ + * @link http://pear.php.net/package/Math_BigInteger + */ + +/**#@+ + * Reduction constants + * + * @access private + * @see Math_BigInteger::_reduce() + */ +/** + * @see Math_BigInteger::_montgomery() + * @see Math_BigInteger::_prepMontgomery() + */ +define('MATH_BIGINTEGER_MONTGOMERY', 0); +/** + * @see Math_BigInteger::_barrett() + */ +define('MATH_BIGINTEGER_BARRETT', 1); +/** + * @see Math_BigInteger::_mod2() + */ +define('MATH_BIGINTEGER_POWEROF2', 2); +/** + * @see Math_BigInteger::_remainder() + */ +define('MATH_BIGINTEGER_CLASSIC', 3); +/** + * @see Math_BigInteger::__clone() + */ +define('MATH_BIGINTEGER_NONE', 4); +/**#@-*/ + +/**#@+ + * Array constants + * + * Rather than create a thousands and thousands of new Math_BigInteger objects in repeated function calls to add() and + * multiply() or whatever, we'll just work directly on arrays, taking them in as parameters and returning them. + * + * @access private + */ +/** + * $result[MATH_BIGINTEGER_VALUE] contains the value. + */ +define('MATH_BIGINTEGER_VALUE', 0); +/** + * $result[MATH_BIGINTEGER_SIGN] contains the sign. + */ +define('MATH_BIGINTEGER_SIGN', 1); +/**#@-*/ + +/**#@+ + * @access private + * @see Math_BigInteger::_montgomery() + * @see Math_BigInteger::_barrett() + */ +/** + * Cache constants + * + * $cache[MATH_BIGINTEGER_VARIABLE] tells us whether or not the cached data is still valid. + */ +define('MATH_BIGINTEGER_VARIABLE', 0); +/** + * $cache[MATH_BIGINTEGER_DATA] contains the cached data. + */ +define('MATH_BIGINTEGER_DATA', 1); +/**#@-*/ + +/**#@+ + * Mode constants. + * + * @access private + * @see Math_BigInteger::Math_BigInteger() + */ +/** + * To use the pure-PHP implementation + */ +define('MATH_BIGINTEGER_MODE_INTERNAL', 1); +/** + * To use the BCMath library + * + * (if enabled; otherwise, the internal implementation will be used) + */ +define('MATH_BIGINTEGER_MODE_BCMATH', 2); +/** + * To use the GMP library + * + * (if present; otherwise, either the BCMath or the internal implementation will be used) + */ +define('MATH_BIGINTEGER_MODE_GMP', 3); +/**#@-*/ + +/** + * The largest digit that may be used in addition / subtraction + * + * (we do pow(2, 52) instead of using 4503599627370496, directly, because some PHP installations + * will truncate 4503599627370496) + * + * @access private + */ +define('MATH_BIGINTEGER_MAX_DIGIT52', pow(2, 52)); + +/** + * Karatsuba Cutoff + * + * At what point do we switch between Karatsuba multiplication and schoolbook long multiplication? + * + * @access private + */ +define('MATH_BIGINTEGER_KARATSUBA_CUTOFF', 25); + +/** + * Pure-PHP arbitrary precision integer arithmetic library. Supports base-2, base-10, base-16, and base-256 + * numbers. + * + * @author Jim Wigginton + * @version 1.0.0RC4 + * @access public + * @package Math_BigInteger + */ +class Math_BigInteger { + /** + * Holds the BigInteger's value. + * + * @var Array + * @access private + */ + var $value; + + /** + * Holds the BigInteger's magnitude. + * + * @var Boolean + * @access private + */ + var $is_negative = false; + + /** + * Random number generator function + * + * @see setRandomGenerator() + * @access private + */ + var $generator = 'mt_rand'; + + /** + * Precision + * + * @see setPrecision() + * @access private + */ + var $precision = -1; + + /** + * Precision Bitmask + * + * @see setPrecision() + * @access private + */ + var $bitmask = false; + + /** + * Mode independant value used for serialization. + * + * If the bcmath or gmp extensions are installed $this->value will be a non-serializable resource, hence the need for + * a variable that'll be serializable regardless of whether or not extensions are being used. Unlike $this->value, + * however, $this->hex is only calculated when $this->__sleep() is called. + * + * @see __sleep() + * @see __wakeup() + * @var String + * @access private + */ + var $hex; + + /** + * Converts base-2, base-10, base-16, and binary strings (eg. base-256) to BigIntegers. + * + * If the second parameter - $base - is negative, then it will be assumed that the number's are encoded using + * two's compliment. The sole exception to this is -10, which is treated the same as 10 is. + * + * Here's an example: + * + * toString(); // outputs 50 + * ?> + * + * + * @param optional $x base-10 number or base-$base number if $base set. + * @param optional integer $base + * @return Math_BigInteger + * @access public + */ + function Math_BigInteger($x = 0, $base = 10) + { + if ( !defined('MATH_BIGINTEGER_MODE') ) { + switch (true) { + case extension_loaded('gmp'): + define('MATH_BIGINTEGER_MODE', MATH_BIGINTEGER_MODE_GMP); + break; + case extension_loaded('bcmath'): + define('MATH_BIGINTEGER_MODE', MATH_BIGINTEGER_MODE_BCMATH); + break; + default: + define('MATH_BIGINTEGER_MODE', MATH_BIGINTEGER_MODE_INTERNAL); + } + } + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + if (is_resource($x) && get_resource_type($x) == 'GMP integer') { + $this->value = $x; + return; + } + $this->value = gmp_init(0); + break; + case MATH_BIGINTEGER_MODE_BCMATH: + $this->value = '0'; + break; + default: + $this->value = array(); + } + + if ($x === 0) { + return; + } + + switch ($base) { + case -256: + if (ord($x[0]) & 0x80) { + $x = ~$x; + $this->is_negative = true; + } + case 256: + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $sign = $this->is_negative ? '-' : ''; + $this->value = gmp_init($sign . '0x' . bin2hex($x)); + break; + case MATH_BIGINTEGER_MODE_BCMATH: + // round $len to the nearest 4 (thanks, DavidMJ!) + $len = (strlen($x) + 3) & 0xFFFFFFFC; + + $x = str_pad($x, $len, chr(0), STR_PAD_LEFT); + + for ($i = 0; $i < $len; $i+= 4) { + $this->value = bcmul($this->value, '4294967296', 0); // 4294967296 == 2**32 + $this->value = bcadd($this->value, 0x1000000 * ord($x[$i]) + ((ord($x[$i + 1]) << 16) | (ord($x[$i + 2]) << 8) | ord($x[$i + 3])), 0); + } + + if ($this->is_negative) { + $this->value = '-' . $this->value; + } + + break; + // converts a base-2**8 (big endian / msb) number to base-2**26 (little endian / lsb) + default: + while (strlen($x)) { + $this->value[] = $this->_bytes2int($this->_base256_rshift($x, 26)); + } + } + + if ($this->is_negative) { + if (MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_INTERNAL) { + $this->is_negative = false; + } + $temp = $this->add(new Math_BigInteger('-1')); + $this->value = $temp->value; + } + break; + case 16: + case -16: + if ($base > 0 && $x[0] == '-') { + $this->is_negative = true; + $x = substr($x, 1); + } + + $x = preg_replace('#^(?:0x)?([A-Fa-f0-9]*).*#', '$1', $x); + + $is_negative = false; + if ($base < 0 && hexdec($x[0]) >= 8) { + $this->is_negative = $is_negative = true; + $x = bin2hex(~pack('H*', $x)); + } + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = $this->is_negative ? '-0x' . $x : '0x' . $x; + $this->value = gmp_init($temp); + $this->is_negative = false; + break; + case MATH_BIGINTEGER_MODE_BCMATH: + $x = ( strlen($x) & 1 ) ? '0' . $x : $x; + $temp = new Math_BigInteger(pack('H*', $x), 256); + $this->value = $this->is_negative ? '-' . $temp->value : $temp->value; + $this->is_negative = false; + break; + default: + $x = ( strlen($x) & 1 ) ? '0' . $x : $x; + $temp = new Math_BigInteger(pack('H*', $x), 256); + $this->value = $temp->value; + } + + if ($is_negative) { + $temp = $this->add(new Math_BigInteger('-1')); + $this->value = $temp->value; + } + break; + case 10: + case -10: + $x = preg_replace('#^(-?[0-9]*).*#', '$1', $x); + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $this->value = gmp_init($x); + break; + case MATH_BIGINTEGER_MODE_BCMATH: + // explicitly casting $x to a string is necessary, here, since doing $x[0] on -1 yields different + // results then doing it on '-1' does (modInverse does $x[0]) + $this->value = (string) $x; + break; + default: + $temp = new Math_BigInteger(); + + // array(10000000) is 10**7 in base-2**26. 10**7 is the closest to 2**26 we can get without passing it. + $multiplier = new Math_BigInteger(); + $multiplier->value = array(10000000); + + if ($x[0] == '-') { + $this->is_negative = true; + $x = substr($x, 1); + } + + $x = str_pad($x, strlen($x) + (6 * strlen($x)) % 7, 0, STR_PAD_LEFT); + + while (strlen($x)) { + $temp = $temp->multiply($multiplier); + $temp = $temp->add(new Math_BigInteger($this->_int2bytes(substr($x, 0, 7)), 256)); + $x = substr($x, 7); + } + + $this->value = $temp->value; + } + break; + case 2: // base-2 support originally implemented by Lluis Pamies - thanks! + case -2: + if ($base > 0 && $x[0] == '-') { + $this->is_negative = true; + $x = substr($x, 1); + } + + $x = preg_replace('#^([01]*).*#', '$1', $x); + $x = str_pad($x, strlen($x) + (3 * strlen($x)) % 4, 0, STR_PAD_LEFT); + + $str = '0x'; + while (strlen($x)) { + $part = substr($x, 0, 4); + $str.= dechex(bindec($part)); + $x = substr($x, 4); + } + + if ($this->is_negative) { + $str = '-' . $str; + } + + $temp = new Math_BigInteger($str, 8 * $base); // ie. either -16 or +16 + $this->value = $temp->value; + $this->is_negative = $temp->is_negative; + + break; + default: + // base not supported, so we'll let $this == 0 + } + } + + /** + * Converts a BigInteger to a byte string (eg. base-256). + * + * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're + * saved as two's compliment. + * + * Here's an example: + * + * toBytes(); // outputs chr(65) + * ?> + * + * + * @param Boolean $twos_compliment + * @return String + * @access public + * @internal Converts a base-2**26 number to base-2**8 + */ + function toBytes($twos_compliment = false) + { + if ($twos_compliment) { + $comparison = $this->compare(new Math_BigInteger()); + if ($comparison == 0) { + return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; + } + + $temp = $comparison < 0 ? $this->add(new Math_BigInteger(1)) : $this->copy(); + $bytes = $temp->toBytes(); + + if (empty($bytes)) { // eg. if the number we're trying to convert is -1 + $bytes = chr(0); + } + + if (ord($bytes[0]) & 0x80) { + $bytes = chr(0) . $bytes; + } + + return $comparison < 0 ? ~$bytes : $bytes; + } + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + if (gmp_cmp($this->value, gmp_init(0)) == 0) { + return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; + } + + $temp = gmp_strval(gmp_abs($this->value), 16); + $temp = ( strlen($temp) & 1 ) ? '0' . $temp : $temp; + $temp = pack('H*', $temp); + + return $this->precision > 0 ? + substr(str_pad($temp, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) : + ltrim($temp, chr(0)); + case MATH_BIGINTEGER_MODE_BCMATH: + if ($this->value === '0') { + return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; + } + + $value = ''; + $current = $this->value; + + if ($current[0] == '-') { + $current = substr($current, 1); + } + + while (bccomp($current, '0', 0) > 0) { + $temp = bcmod($current, '16777216'); + $value = chr($temp >> 16) . chr($temp >> 8) . chr($temp) . $value; + $current = bcdiv($current, '16777216', 0); + } + + return $this->precision > 0 ? + substr(str_pad($value, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) : + ltrim($value, chr(0)); + } + + if (!count($this->value)) { + return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; + } + $result = $this->_int2bytes($this->value[count($this->value) - 1]); + + $temp = $this->copy(); + + for ($i = count($temp->value) - 2; $i >= 0; --$i) { + $temp->_base256_lshift($result, 26); + $result = $result | str_pad($temp->_int2bytes($temp->value[$i]), strlen($result), chr(0), STR_PAD_LEFT); + } + + return $this->precision > 0 ? + str_pad(substr($result, -(($this->precision + 7) >> 3)), ($this->precision + 7) >> 3, chr(0), STR_PAD_LEFT) : + $result; + } + + /** + * Converts a BigInteger to a hex string (eg. base-16)). + * + * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're + * saved as two's compliment. + * + * Here's an example: + * + * toHex(); // outputs '41' + * ?> + * + * + * @param Boolean $twos_compliment + * @return String + * @access public + * @internal Converts a base-2**26 number to base-2**8 + */ + function toHex($twos_compliment = false) + { + return bin2hex($this->toBytes($twos_compliment)); + } + + /** + * Converts a BigInteger to a bit string (eg. base-2). + * + * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're + * saved as two's compliment. + * + * Here's an example: + * + * toBits(); // outputs '1000001' + * ?> + * + * + * @param Boolean $twos_compliment + * @return String + * @access public + * @internal Converts a base-2**26 number to base-2**2 + */ + function toBits($twos_compliment = false) + { + $hex = $this->toHex($twos_compliment); + $bits = ''; + for ($i = 0; $i < strlen($hex); $i+=8) { + $bits.= str_pad(decbin(hexdec(substr($hex, $i, 8))), 32, '0', STR_PAD_LEFT); + } + return $this->precision > 0 ? substr($bits, -$this->precision) : ltrim($bits, '0'); + } + + /** + * Converts a BigInteger to a base-10 number. + * + * Here's an example: + * + * toString(); // outputs 50 + * ?> + * + * + * @return String + * @access public + * @internal Converts a base-2**26 number to base-10**7 (which is pretty much base-10) + */ + function toString() + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + return gmp_strval($this->value); + case MATH_BIGINTEGER_MODE_BCMATH: + if ($this->value === '0') { + return '0'; + } + + return ltrim($this->value, '0'); + } + + if (!count($this->value)) { + return '0'; + } + + $temp = $this->copy(); + $temp->is_negative = false; + + $divisor = new Math_BigInteger(); + $divisor->value = array(10000000); // eg. 10**7 + $result = ''; + while (count($temp->value)) { + list($temp, $mod) = $temp->divide($divisor); + $result = str_pad(isset($mod->value[0]) ? $mod->value[0] : '', 7, '0', STR_PAD_LEFT) . $result; + } + $result = ltrim($result, '0'); + if (empty($result)) { + $result = '0'; + } + + if ($this->is_negative) { + $result = '-' . $result; + } + + return $result; + } + + /** + * Copy an object + * + * PHP5 passes objects by reference while PHP4 passes by value. As such, we need a function to guarantee + * that all objects are passed by value, when appropriate. More information can be found here: + * + * {@link http://php.net/language.oop5.basic#51624} + * + * @access public + * @see __clone() + * @return Math_BigInteger + */ + function copy() + { + $temp = new Math_BigInteger(); + $temp->value = $this->value; + $temp->is_negative = $this->is_negative; + $temp->generator = $this->generator; + $temp->precision = $this->precision; + $temp->bitmask = $this->bitmask; + return $temp; + } + + /** + * __toString() magic method + * + * Will be called, automatically, if you're supporting just PHP5. If you're supporting PHP4, you'll need to call + * toString(). + * + * @access public + * @internal Implemented per a suggestion by Techie-Michael - thanks! + */ + function __toString() + { + return $this->toString(); + } + + /** + * __clone() magic method + * + * Although you can call Math_BigInteger::__toString() directly in PHP5, you cannot call Math_BigInteger::__clone() + * directly in PHP5. You can in PHP4 since it's not a magic method, but in PHP5, you have to call it by using the PHP5 + * only syntax of $y = clone $x. As such, if you're trying to write an application that works on both PHP4 and PHP5, + * call Math_BigInteger::copy(), instead. + * + * @access public + * @see copy() + * @return Math_BigInteger + */ + function __clone() + { + return $this->copy(); + } + + /** + * __sleep() magic method + * + * Will be called, automatically, when serialize() is called on a Math_BigInteger object. + * + * @see __wakeup + * @access public + */ + function __sleep() + { + $this->hex = $this->toHex(true); + $vars = array('hex'); + if ($this->generator != 'mt_rand') { + $vars[] = 'generator'; + } + if ($this->precision > 0) { + $vars[] = 'precision'; + } + return $vars; + + } + + /** + * __wakeup() magic method + * + * Will be called, automatically, when unserialize() is called on a Math_BigInteger object. + * + * @see __sleep + * @access public + */ + function __wakeup() + { + $temp = new Math_BigInteger($this->hex, -16); + $this->value = $temp->value; + $this->is_negative = $temp->is_negative; + $this->setRandomGenerator($this->generator); + if ($this->precision > 0) { + // recalculate $this->bitmask + $this->setPrecision($this->precision); + } + } + + /** + * Adds two BigIntegers. + * + * Here's an example: + * + * add($b); + * + * echo $c->toString(); // outputs 30 + * ?> + * + * + * @param Math_BigInteger $y + * @return Math_BigInteger + * @access public + * @internal Performs base-2**52 addition + */ + function add($y) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_add($this->value, $y->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $temp = new Math_BigInteger(); + $temp->value = bcadd($this->value, $y->value, 0); + + return $this->_normalize($temp); + } + + $temp = $this->_add($this->value, $this->is_negative, $y->value, $y->is_negative); + + $result = new Math_BigInteger(); + $result->value = $temp[MATH_BIGINTEGER_VALUE]; + $result->is_negative = $temp[MATH_BIGINTEGER_SIGN]; + + return $this->_normalize($result); + } + + /** + * Performs addition. + * + * @param Array $x_value + * @param Boolean $x_negative + * @param Array $y_value + * @param Boolean $y_negative + * @return Array + * @access private + */ + function _add($x_value, $x_negative, $y_value, $y_negative) + { + $x_size = count($x_value); + $y_size = count($y_value); + + if ($x_size == 0) { + return array( + MATH_BIGINTEGER_VALUE => $y_value, + MATH_BIGINTEGER_SIGN => $y_negative + ); + } else if ($y_size == 0) { + return array( + MATH_BIGINTEGER_VALUE => $x_value, + MATH_BIGINTEGER_SIGN => $x_negative + ); + } + + // subtract, if appropriate + if ( $x_negative != $y_negative ) { + if ( $x_value == $y_value ) { + return array( + MATH_BIGINTEGER_VALUE => array(), + MATH_BIGINTEGER_SIGN => false + ); + } + + $temp = $this->_subtract($x_value, false, $y_value, false); + $temp[MATH_BIGINTEGER_SIGN] = $this->_compare($x_value, false, $y_value, false) > 0 ? + $x_negative : $y_negative; + + return $temp; + } + + if ($x_size < $y_size) { + $size = $x_size; + $value = $y_value; + } else { + $size = $y_size; + $value = $x_value; + } + + $value[] = 0; // just in case the carry adds an extra digit + + $carry = 0; + for ($i = 0, $j = 1; $j < $size; $i+=2, $j+=2) { + $sum = $x_value[$j] * 0x4000000 + $x_value[$i] + $y_value[$j] * 0x4000000 + $y_value[$i] + $carry; + $carry = $sum >= MATH_BIGINTEGER_MAX_DIGIT52; // eg. floor($sum / 2**52); only possible values (in any base) are 0 and 1 + $sum = $carry ? $sum - MATH_BIGINTEGER_MAX_DIGIT52 : $sum; + + $temp = (int) ($sum / 0x4000000); + + $value[$i] = (int) ($sum - 0x4000000 * $temp); // eg. a faster alternative to fmod($sum, 0x4000000) + $value[$j] = $temp; + } + + if ($j == $size) { // ie. if $y_size is odd + $sum = $x_value[$i] + $y_value[$i] + $carry; + $carry = $sum >= 0x4000000; + $value[$i] = $carry ? $sum - 0x4000000 : $sum; + ++$i; // ie. let $i = $j since we've just done $value[$i] + } + + if ($carry) { + for (; $value[$i] == 0x3FFFFFF; ++$i) { + $value[$i] = 0; + } + ++$value[$i]; + } + + return array( + MATH_BIGINTEGER_VALUE => $this->_trim($value), + MATH_BIGINTEGER_SIGN => $x_negative + ); + } + + /** + * Subtracts two BigIntegers. + * + * Here's an example: + * + * subtract($b); + * + * echo $c->toString(); // outputs -10 + * ?> + * + * + * @param Math_BigInteger $y + * @return Math_BigInteger + * @access public + * @internal Performs base-2**52 subtraction + */ + function subtract($y) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_sub($this->value, $y->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $temp = new Math_BigInteger(); + $temp->value = bcsub($this->value, $y->value, 0); + + return $this->_normalize($temp); + } + + $temp = $this->_subtract($this->value, $this->is_negative, $y->value, $y->is_negative); + + $result = new Math_BigInteger(); + $result->value = $temp[MATH_BIGINTEGER_VALUE]; + $result->is_negative = $temp[MATH_BIGINTEGER_SIGN]; + + return $this->_normalize($result); + } + + /** + * Performs subtraction. + * + * @param Array $x_value + * @param Boolean $x_negative + * @param Array $y_value + * @param Boolean $y_negative + * @return Array + * @access private + */ + function _subtract($x_value, $x_negative, $y_value, $y_negative) + { + $x_size = count($x_value); + $y_size = count($y_value); + + if ($x_size == 0) { + return array( + MATH_BIGINTEGER_VALUE => $y_value, + MATH_BIGINTEGER_SIGN => !$y_negative + ); + } else if ($y_size == 0) { + return array( + MATH_BIGINTEGER_VALUE => $x_value, + MATH_BIGINTEGER_SIGN => $x_negative + ); + } + + // add, if appropriate (ie. -$x - +$y or +$x - -$y) + if ( $x_negative != $y_negative ) { + $temp = $this->_add($x_value, false, $y_value, false); + $temp[MATH_BIGINTEGER_SIGN] = $x_negative; + + return $temp; + } + + $diff = $this->_compare($x_value, $x_negative, $y_value, $y_negative); + + if ( !$diff ) { + return array( + MATH_BIGINTEGER_VALUE => array(), + MATH_BIGINTEGER_SIGN => false + ); + } + + // switch $x and $y around, if appropriate. + if ( (!$x_negative && $diff < 0) || ($x_negative && $diff > 0) ) { + $temp = $x_value; + $x_value = $y_value; + $y_value = $temp; + + $x_negative = !$x_negative; + + $x_size = count($x_value); + $y_size = count($y_value); + } + + // at this point, $x_value should be at least as big as - if not bigger than - $y_value + + $carry = 0; + for ($i = 0, $j = 1; $j < $y_size; $i+=2, $j+=2) { + $sum = $x_value[$j] * 0x4000000 + $x_value[$i] - $y_value[$j] * 0x4000000 - $y_value[$i] - $carry; + $carry = $sum < 0; // eg. floor($sum / 2**52); only possible values (in any base) are 0 and 1 + $sum = $carry ? $sum + MATH_BIGINTEGER_MAX_DIGIT52 : $sum; + + $temp = (int) ($sum / 0x4000000); + + $x_value[$i] = (int) ($sum - 0x4000000 * $temp); + $x_value[$j] = $temp; + } + + if ($j == $y_size) { // ie. if $y_size is odd + $sum = $x_value[$i] - $y_value[$i] - $carry; + $carry = $sum < 0; + $x_value[$i] = $carry ? $sum + 0x4000000 : $sum; + ++$i; + } + + if ($carry) { + for (; !$x_value[$i]; ++$i) { + $x_value[$i] = 0x3FFFFFF; + } + --$x_value[$i]; + } + + return array( + MATH_BIGINTEGER_VALUE => $this->_trim($x_value), + MATH_BIGINTEGER_SIGN => $x_negative + ); + } + + /** + * Multiplies two BigIntegers + * + * Here's an example: + * + * multiply($b); + * + * echo $c->toString(); // outputs 200 + * ?> + * + * + * @param Math_BigInteger $x + * @return Math_BigInteger + * @access public + */ + function multiply($x) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_mul($this->value, $x->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $temp = new Math_BigInteger(); + $temp->value = bcmul($this->value, $x->value, 0); + + return $this->_normalize($temp); + } + + $temp = $this->_multiply($this->value, $this->is_negative, $x->value, $x->is_negative); + + $product = new Math_BigInteger(); + $product->value = $temp[MATH_BIGINTEGER_VALUE]; + $product->is_negative = $temp[MATH_BIGINTEGER_SIGN]; + + return $this->_normalize($product); + } + + /** + * Performs multiplication. + * + * @param Array $x_value + * @param Boolean $x_negative + * @param Array $y_value + * @param Boolean $y_negative + * @return Array + * @access private + */ + function _multiply($x_value, $x_negative, $y_value, $y_negative) + { + //if ( $x_value == $y_value ) { + // return array( + // MATH_BIGINTEGER_VALUE => $this->_square($x_value), + // MATH_BIGINTEGER_SIGN => $x_sign != $y_value + // ); + //} + + $x_length = count($x_value); + $y_length = count($y_value); + + if ( !$x_length || !$y_length ) { // a 0 is being multiplied + return array( + MATH_BIGINTEGER_VALUE => array(), + MATH_BIGINTEGER_SIGN => false + ); + } + + return array( + MATH_BIGINTEGER_VALUE => min($x_length, $y_length) < 2 * MATH_BIGINTEGER_KARATSUBA_CUTOFF ? + $this->_trim($this->_regularMultiply($x_value, $y_value)) : + $this->_trim($this->_karatsuba($x_value, $y_value)), + MATH_BIGINTEGER_SIGN => $x_negative != $y_negative + ); + } + + /** + * Performs long multiplication on two BigIntegers + * + * Modeled after 'multiply' in MutableBigInteger.java. + * + * @param Array $x_value + * @param Array $y_value + * @return Array + * @access private + */ + function _regularMultiply($x_value, $y_value) + { + $x_length = count($x_value); + $y_length = count($y_value); + + if ( !$x_length || !$y_length ) { // a 0 is being multiplied + return array(); + } + + if ( $x_length < $y_length ) { + $temp = $x_value; + $x_value = $y_value; + $y_value = $temp; + + $x_length = count($x_value); + $y_length = count($y_value); + } + + $product_value = $this->_array_repeat(0, $x_length + $y_length); + + // the following for loop could be removed if the for loop following it + // (the one with nested for loops) initially set $i to 0, but + // doing so would also make the result in one set of unnecessary adds, + // since on the outermost loops first pass, $product->value[$k] is going + // to always be 0 + + $carry = 0; + + for ($j = 0; $j < $x_length; ++$j) { // ie. $i = 0 + $temp = $x_value[$j] * $y_value[0] + $carry; // $product_value[$k] == 0 + $carry = (int) ($temp / 0x4000000); + $product_value[$j] = (int) ($temp - 0x4000000 * $carry); + } + + $product_value[$j] = $carry; + + // the above for loop is what the previous comment was talking about. the + // following for loop is the "one with nested for loops" + for ($i = 1; $i < $y_length; ++$i) { + $carry = 0; + + for ($j = 0, $k = $i; $j < $x_length; ++$j, ++$k) { + $temp = $product_value[$k] + $x_value[$j] * $y_value[$i] + $carry; + $carry = (int) ($temp / 0x4000000); + $product_value[$k] = (int) ($temp - 0x4000000 * $carry); + } + + $product_value[$k] = $carry; + } + + return $product_value; + } + + /** + * Performs Karatsuba multiplication on two BigIntegers + * + * See {@link http://en.wikipedia.org/wiki/Karatsuba_algorithm Karatsuba algorithm} and + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=120 MPM 5.2.3}. + * + * @param Array $x_value + * @param Array $y_value + * @return Array + * @access private + */ + function _karatsuba($x_value, $y_value) + { + $m = min(count($x_value) >> 1, count($y_value) >> 1); + + if ($m < MATH_BIGINTEGER_KARATSUBA_CUTOFF) { + return $this->_regularMultiply($x_value, $y_value); + } + + $x1 = array_slice($x_value, $m); + $x0 = array_slice($x_value, 0, $m); + $y1 = array_slice($y_value, $m); + $y0 = array_slice($y_value, 0, $m); + + $z2 = $this->_karatsuba($x1, $y1); + $z0 = $this->_karatsuba($x0, $y0); + + $z1 = $this->_add($x1, false, $x0, false); + $temp = $this->_add($y1, false, $y0, false); + $z1 = $this->_karatsuba($z1[MATH_BIGINTEGER_VALUE], $temp[MATH_BIGINTEGER_VALUE]); + $temp = $this->_add($z2, false, $z0, false); + $z1 = $this->_subtract($z1, false, $temp[MATH_BIGINTEGER_VALUE], false); + + $z2 = array_merge(array_fill(0, 2 * $m, 0), $z2); + $z1[MATH_BIGINTEGER_VALUE] = array_merge(array_fill(0, $m, 0), $z1[MATH_BIGINTEGER_VALUE]); + + $xy = $this->_add($z2, false, $z1[MATH_BIGINTEGER_VALUE], $z1[MATH_BIGINTEGER_SIGN]); + $xy = $this->_add($xy[MATH_BIGINTEGER_VALUE], $xy[MATH_BIGINTEGER_SIGN], $z0, false); + + return $xy[MATH_BIGINTEGER_VALUE]; + } + + /** + * Performs squaring + * + * @param Array $x + * @return Array + * @access private + */ + function _square($x = false) + { + return count($x) < 2 * MATH_BIGINTEGER_KARATSUBA_CUTOFF ? + $this->_trim($this->_baseSquare($x)) : + $this->_trim($this->_karatsubaSquare($x)); + } + + /** + * Performs traditional squaring on two BigIntegers + * + * Squaring can be done faster than multiplying a number by itself can be. See + * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=7 HAC 14.2.4} / + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=141 MPM 5.3} for more information. + * + * @param Array $value + * @return Array + * @access private + */ + function _baseSquare($value) + { + if ( empty($value) ) { + return array(); + } + $square_value = $this->_array_repeat(0, 2 * count($value)); + + for ($i = 0, $max_index = count($value) - 1; $i <= $max_index; ++$i) { + $i2 = $i << 1; + + $temp = $square_value[$i2] + $value[$i] * $value[$i]; + $carry = (int) ($temp / 0x4000000); + $square_value[$i2] = (int) ($temp - 0x4000000 * $carry); + + // note how we start from $i+1 instead of 0 as we do in multiplication. + for ($j = $i + 1, $k = $i2 + 1; $j <= $max_index; ++$j, ++$k) { + $temp = $square_value[$k] + 2 * $value[$j] * $value[$i] + $carry; + $carry = (int) ($temp / 0x4000000); + $square_value[$k] = (int) ($temp - 0x4000000 * $carry); + } + + // the following line can yield values larger 2**15. at this point, PHP should switch + // over to floats. + $square_value[$i + $max_index + 1] = $carry; + } + + return $square_value; + } + + /** + * Performs Karatsuba "squaring" on two BigIntegers + * + * See {@link http://en.wikipedia.org/wiki/Karatsuba_algorithm Karatsuba algorithm} and + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=151 MPM 5.3.4}. + * + * @param Array $value + * @return Array + * @access private + */ + function _karatsubaSquare($value) + { + $m = count($value) >> 1; + + if ($m < MATH_BIGINTEGER_KARATSUBA_CUTOFF) { + return $this->_baseSquare($value); + } + + $x1 = array_slice($value, $m); + $x0 = array_slice($value, 0, $m); + + $z2 = $this->_karatsubaSquare($x1); + $z0 = $this->_karatsubaSquare($x0); + + $z1 = $this->_add($x1, false, $x0, false); + $z1 = $this->_karatsubaSquare($z1[MATH_BIGINTEGER_VALUE]); + $temp = $this->_add($z2, false, $z0, false); + $z1 = $this->_subtract($z1, false, $temp[MATH_BIGINTEGER_VALUE], false); + + $z2 = array_merge(array_fill(0, 2 * $m, 0), $z2); + $z1[MATH_BIGINTEGER_VALUE] = array_merge(array_fill(0, $m, 0), $z1[MATH_BIGINTEGER_VALUE]); + + $xx = $this->_add($z2, false, $z1[MATH_BIGINTEGER_VALUE], $z1[MATH_BIGINTEGER_SIGN]); + $xx = $this->_add($xx[MATH_BIGINTEGER_VALUE], $xx[MATH_BIGINTEGER_SIGN], $z0, false); + + return $xx[MATH_BIGINTEGER_VALUE]; + } + + /** + * Divides two BigIntegers. + * + * Returns an array whose first element contains the quotient and whose second element contains the + * "common residue". If the remainder would be positive, the "common residue" and the remainder are the + * same. If the remainder would be negative, the "common residue" is equal to the sum of the remainder + * and the divisor (basically, the "common residue" is the first positive modulo). + * + * Here's an example: + * + * divide($b); + * + * echo $quotient->toString(); // outputs 0 + * echo "\r\n"; + * echo $remainder->toString(); // outputs 10 + * ?> + * + * + * @param Math_BigInteger $y + * @return Array + * @access public + * @internal This function is based off of {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=9 HAC 14.20}. + */ + function divide($y) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $quotient = new Math_BigInteger(); + $remainder = new Math_BigInteger(); + + list($quotient->value, $remainder->value) = gmp_div_qr($this->value, $y->value); + + if (gmp_sign($remainder->value) < 0) { + $remainder->value = gmp_add($remainder->value, gmp_abs($y->value)); + } + + return array($this->_normalize($quotient), $this->_normalize($remainder)); + case MATH_BIGINTEGER_MODE_BCMATH: + $quotient = new Math_BigInteger(); + $remainder = new Math_BigInteger(); + + $quotient->value = bcdiv($this->value, $y->value, 0); + $remainder->value = bcmod($this->value, $y->value); + + if ($remainder->value[0] == '-') { + $remainder->value = bcadd($remainder->value, $y->value[0] == '-' ? substr($y->value, 1) : $y->value, 0); + } + + return array($this->_normalize($quotient), $this->_normalize($remainder)); + } + + if (count($y->value) == 1) { + list($q, $r) = $this->_divide_digit($this->value, $y->value[0]); + $quotient = new Math_BigInteger(); + $remainder = new Math_BigInteger(); + $quotient->value = $q; + $remainder->value = array($r); + $quotient->is_negative = $this->is_negative != $y->is_negative; + return array($this->_normalize($quotient), $this->_normalize($remainder)); + } + + static $zero; + if ( !isset($zero) ) { + $zero = new Math_BigInteger(); + } + + $x = $this->copy(); + $y = $y->copy(); + + $x_sign = $x->is_negative; + $y_sign = $y->is_negative; + + $x->is_negative = $y->is_negative = false; + + $diff = $x->compare($y); + + if ( !$diff ) { + $temp = new Math_BigInteger(); + $temp->value = array(1); + $temp->is_negative = $x_sign != $y_sign; + return array($this->_normalize($temp), $this->_normalize(new Math_BigInteger())); + } + + if ( $diff < 0 ) { + // if $x is negative, "add" $y. + if ( $x_sign ) { + $x = $y->subtract($x); + } + return array($this->_normalize(new Math_BigInteger()), $this->_normalize($x)); + } + + // normalize $x and $y as described in HAC 14.23 / 14.24 + $msb = $y->value[count($y->value) - 1]; + for ($shift = 0; !($msb & 0x2000000); ++$shift) { + $msb <<= 1; + } + $x->_lshift($shift); + $y->_lshift($shift); + $y_value = &$y->value; + + $x_max = count($x->value) - 1; + $y_max = count($y->value) - 1; + + $quotient = new Math_BigInteger(); + $quotient_value = &$quotient->value; + $quotient_value = $this->_array_repeat(0, $x_max - $y_max + 1); + + static $temp, $lhs, $rhs; + if (!isset($temp)) { + $temp = new Math_BigInteger(); + $lhs = new Math_BigInteger(); + $rhs = new Math_BigInteger(); + } + $temp_value = &$temp->value; + $rhs_value = &$rhs->value; + + // $temp = $y << ($x_max - $y_max-1) in base 2**26 + $temp_value = array_merge($this->_array_repeat(0, $x_max - $y_max), $y_value); + + while ( $x->compare($temp) >= 0 ) { + // calculate the "common residue" + ++$quotient_value[$x_max - $y_max]; + $x = $x->subtract($temp); + $x_max = count($x->value) - 1; + } + + for ($i = $x_max; $i >= $y_max + 1; --$i) { + $x_value = &$x->value; + $x_window = array( + isset($x_value[$i]) ? $x_value[$i] : 0, + isset($x_value[$i - 1]) ? $x_value[$i - 1] : 0, + isset($x_value[$i - 2]) ? $x_value[$i - 2] : 0 + ); + $y_window = array( + $y_value[$y_max], + ( $y_max > 0 ) ? $y_value[$y_max - 1] : 0 + ); + + $q_index = $i - $y_max - 1; + if ($x_window[0] == $y_window[0]) { + $quotient_value[$q_index] = 0x3FFFFFF; + } else { + $quotient_value[$q_index] = (int) ( + ($x_window[0] * 0x4000000 + $x_window[1]) + / + $y_window[0] + ); + } + + $temp_value = array($y_window[1], $y_window[0]); + + $lhs->value = array($quotient_value[$q_index]); + $lhs = $lhs->multiply($temp); + + $rhs_value = array($x_window[2], $x_window[1], $x_window[0]); + + while ( $lhs->compare($rhs) > 0 ) { + --$quotient_value[$q_index]; + + $lhs->value = array($quotient_value[$q_index]); + $lhs = $lhs->multiply($temp); + } + + $adjust = $this->_array_repeat(0, $q_index); + $temp_value = array($quotient_value[$q_index]); + $temp = $temp->multiply($y); + $temp_value = &$temp->value; + $temp_value = array_merge($adjust, $temp_value); + + $x = $x->subtract($temp); + + if ($x->compare($zero) < 0) { + $temp_value = array_merge($adjust, $y_value); + $x = $x->add($temp); + + --$quotient_value[$q_index]; + } + + $x_max = count($x_value) - 1; + } + + // unnormalize the remainder + $x->_rshift($shift); + + $quotient->is_negative = $x_sign != $y_sign; + + // calculate the "common residue", if appropriate + if ( $x_sign ) { + $y->_rshift($shift); + $x = $y->subtract($x); + } + + return array($this->_normalize($quotient), $this->_normalize($x)); + } + + /** + * Divides a BigInteger by a regular integer + * + * abc / x = a00 / x + b0 / x + c / x + * + * @param Array $dividend + * @param Array $divisor + * @return Array + * @access private + */ + function _divide_digit($dividend, $divisor) + { + $carry = 0; + $result = array(); + + for ($i = count($dividend) - 1; $i >= 0; --$i) { + $temp = 0x4000000 * $carry + $dividend[$i]; + $result[$i] = (int) ($temp / $divisor); + $carry = (int) ($temp - $divisor * $result[$i]); + } + + return array($result, $carry); + } + + /** + * Performs modular exponentiation. + * + * Here's an example: + * + * modPow($b, $c); + * + * echo $c->toString(); // outputs 10 + * ?> + * + * + * @param Math_BigInteger $e + * @param Math_BigInteger $n + * @return Math_BigInteger + * @access public + * @internal The most naive approach to modular exponentiation has very unreasonable requirements, and + * and although the approach involving repeated squaring does vastly better, it, too, is impractical + * for our purposes. The reason being that division - by far the most complicated and time-consuming + * of the basic operations (eg. +,-,*,/) - occurs multiple times within it. + * + * Modular reductions resolve this issue. Although an individual modular reduction takes more time + * then an individual division, when performed in succession (with the same modulo), they're a lot faster. + * + * The two most commonly used modular reductions are Barrett and Montgomery reduction. Montgomery reduction, + * although faster, only works when the gcd of the modulo and of the base being used is 1. In RSA, when the + * base is a power of two, the modulo - a product of two primes - is always going to have a gcd of 1 (because + * the product of two odd numbers is odd), but what about when RSA isn't used? + * + * In contrast, Barrett reduction has no such constraint. As such, some bigint implementations perform a + * Barrett reduction after every operation in the modpow function. Others perform Barrett reductions when the + * modulo is even and Montgomery reductions when the modulo is odd. BigInteger.java's modPow method, however, + * uses a trick involving the Chinese Remainder Theorem to factor the even modulo into two numbers - one odd and + * the other, a power of two - and recombine them, later. This is the method that this modPow function uses. + * {@link http://islab.oregonstate.edu/papers/j34monex.pdf Montgomery Reduction with Even Modulus} elaborates. + */ + function modPow($e, $n) + { + $n = $this->bitmask !== false && $this->bitmask->compare($n) < 0 ? $this->bitmask : $n->abs(); + + if ($e->compare(new Math_BigInteger()) < 0) { + $e = $e->abs(); + + $temp = $this->modInverse($n); + if ($temp === false) { + return false; + } + + return $this->_normalize($temp->modPow($e, $n)); + } + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_powm($this->value, $e->value, $n->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $temp = new Math_BigInteger(); + $temp->value = bcpowmod($this->value, $e->value, $n->value, 0); + + return $this->_normalize($temp); + } + + if ( empty($e->value) ) { + $temp = new Math_BigInteger(); + $temp->value = array(1); + return $this->_normalize($temp); + } + + if ( $e->value == array(1) ) { + list(, $temp) = $this->divide($n); + return $this->_normalize($temp); + } + + if ( $e->value == array(2) ) { + $temp = new Math_BigInteger(); + $temp->value = $this->_square($this->value); + list(, $temp) = $temp->divide($n); + return $this->_normalize($temp); + } + + return $this->_normalize($this->_slidingWindow($e, $n, MATH_BIGINTEGER_BARRETT)); + + // is the modulo odd? + if ( $n->value[0] & 1 ) { + return $this->_normalize($this->_slidingWindow($e, $n, MATH_BIGINTEGER_MONTGOMERY)); + } + // if it's not, it's even + + // find the lowest set bit (eg. the max pow of 2 that divides $n) + for ($i = 0; $i < count($n->value); ++$i) { + if ( $n->value[$i] ) { + $temp = decbin($n->value[$i]); + $j = strlen($temp) - strrpos($temp, '1') - 1; + $j+= 26 * $i; + break; + } + } + // at this point, 2^$j * $n/(2^$j) == $n + + $mod1 = $n->copy(); + $mod1->_rshift($j); + $mod2 = new Math_BigInteger(); + $mod2->value = array(1); + $mod2->_lshift($j); + + $part1 = ( $mod1->value != array(1) ) ? $this->_slidingWindow($e, $mod1, MATH_BIGINTEGER_MONTGOMERY) : new Math_BigInteger(); + $part2 = $this->_slidingWindow($e, $mod2, MATH_BIGINTEGER_POWEROF2); + + $y1 = $mod2->modInverse($mod1); + $y2 = $mod1->modInverse($mod2); + + $result = $part1->multiply($mod2); + $result = $result->multiply($y1); + + $temp = $part2->multiply($mod1); + $temp = $temp->multiply($y2); + + $result = $result->add($temp); + list(, $result) = $result->divide($n); + + return $this->_normalize($result); + } + + /** + * Performs modular exponentiation. + * + * Alias for Math_BigInteger::modPow() + * + * @param Math_BigInteger $e + * @param Math_BigInteger $n + * @return Math_BigInteger + * @access public + */ + function powMod($e, $n) + { + return $this->modPow($e, $n); + } + + /** + * Sliding Window k-ary Modular Exponentiation + * + * Based on {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=27 HAC 14.85} / + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=210 MPM 7.7}. In a departure from those algorithims, + * however, this function performs a modular reduction after every multiplication and squaring operation. + * As such, this function has the same preconditions that the reductions being used do. + * + * @param Math_BigInteger $e + * @param Math_BigInteger $n + * @param Integer $mode + * @return Math_BigInteger + * @access private + */ + function _slidingWindow($e, $n, $mode) + { + static $window_ranges = array(7, 25, 81, 241, 673, 1793); // from BigInteger.java's oddModPow function + //static $window_ranges = array(0, 7, 36, 140, 450, 1303, 3529); // from MPM 7.3.1 + + $e_value = $e->value; + $e_length = count($e_value) - 1; + $e_bits = decbin($e_value[$e_length]); + for ($i = $e_length - 1; $i >= 0; --$i) { + $e_bits.= str_pad(decbin($e_value[$i]), 26, '0', STR_PAD_LEFT); + } + + $e_length = strlen($e_bits); + + // calculate the appropriate window size. + // $window_size == 3 if $window_ranges is between 25 and 81, for example. + for ($i = 0, $window_size = 1; $e_length > $window_ranges[$i] && $i < count($window_ranges); ++$window_size, ++$i); + + $n_value = $n->value; + + // precompute $this^0 through $this^$window_size + $powers = array(); + $powers[1] = $this->_prepareReduce($this->value, $n_value, $mode); + $powers[2] = $this->_squareReduce($powers[1], $n_value, $mode); + + // we do every other number since substr($e_bits, $i, $j+1) (see below) is supposed to end + // in a 1. ie. it's supposed to be odd. + $temp = 1 << ($window_size - 1); + for ($i = 1; $i < $temp; ++$i) { + $i2 = $i << 1; + $powers[$i2 + 1] = $this->_multiplyReduce($powers[$i2 - 1], $powers[2], $n_value, $mode); + } + + $result = array(1); + $result = $this->_prepareReduce($result, $n_value, $mode); + + for ($i = 0; $i < $e_length; ) { + if ( !$e_bits[$i] ) { + $result = $this->_squareReduce($result, $n_value, $mode); + ++$i; + } else { + for ($j = $window_size - 1; $j > 0; --$j) { + if ( !empty($e_bits[$i + $j]) ) { + break; + } + } + + for ($k = 0; $k <= $j; ++$k) {// eg. the length of substr($e_bits, $i, $j+1) + $result = $this->_squareReduce($result, $n_value, $mode); + } + + $result = $this->_multiplyReduce($result, $powers[bindec(substr($e_bits, $i, $j + 1))], $n_value, $mode); + + $i+=$j + 1; + } + } + + $temp = new Math_BigInteger(); + $temp->value = $this->_reduce($result, $n_value, $mode); + + return $temp; + } + + /** + * Modular reduction + * + * For most $modes this will return the remainder. + * + * @see _slidingWindow() + * @access private + * @param Array $x + * @param Array $n + * @param Integer $mode + * @return Array + */ + function _reduce($x, $n, $mode) + { + switch ($mode) { + case MATH_BIGINTEGER_MONTGOMERY: + return $this->_montgomery($x, $n); + case MATH_BIGINTEGER_BARRETT: + return $this->_barrett($x, $n); + case MATH_BIGINTEGER_POWEROF2: + $lhs = new Math_BigInteger(); + $lhs->value = $x; + $rhs = new Math_BigInteger(); + $rhs->value = $n; + return $x->_mod2($n); + case MATH_BIGINTEGER_CLASSIC: + $lhs = new Math_BigInteger(); + $lhs->value = $x; + $rhs = new Math_BigInteger(); + $rhs->value = $n; + list(, $temp) = $lhs->divide($rhs); + return $temp->value; + case MATH_BIGINTEGER_NONE: + return $x; + default: + // an invalid $mode was provided + } + } + + /** + * Modular reduction preperation + * + * @see _slidingWindow() + * @access private + * @param Array $x + * @param Array $n + * @param Integer $mode + * @return Array + */ + function _prepareReduce($x, $n, $mode) + { + if ($mode == MATH_BIGINTEGER_MONTGOMERY) { + return $this->_prepMontgomery($x, $n); + } + return $this->_reduce($x, $n, $mode); + } + + /** + * Modular multiply + * + * @see _slidingWindow() + * @access private + * @param Array $x + * @param Array $y + * @param Array $n + * @param Integer $mode + * @return Array + */ + function _multiplyReduce($x, $y, $n, $mode) + { + if ($mode == MATH_BIGINTEGER_MONTGOMERY) { + return $this->_montgomeryMultiply($x, $y, $n); + } + $temp = $this->_multiply($x, false, $y, false); + return $this->_reduce($temp[MATH_BIGINTEGER_VALUE], $n, $mode); + } + + /** + * Modular square + * + * @see _slidingWindow() + * @access private + * @param Array $x + * @param Array $n + * @param Integer $mode + * @return Array + */ + function _squareReduce($x, $n, $mode) + { + if ($mode == MATH_BIGINTEGER_MONTGOMERY) { + return $this->_montgomeryMultiply($x, $x, $n); + } + return $this->_reduce($this->_square($x), $n, $mode); + } + + /** + * Modulos for Powers of Two + * + * Calculates $x%$n, where $n = 2**$e, for some $e. Since this is basically the same as doing $x & ($n-1), + * we'll just use this function as a wrapper for doing that. + * + * @see _slidingWindow() + * @access private + * @param Math_BigInteger + * @return Math_BigInteger + */ + function _mod2($n) + { + $temp = new Math_BigInteger(); + $temp->value = array(1); + return $this->bitwise_and($n->subtract($temp)); + } + + /** + * Barrett Modular Reduction + * + * See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=14 HAC 14.3.3} / + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=165 MPM 6.2.5} for more information. Modified slightly, + * so as not to require negative numbers (initially, this script didn't support negative numbers). + * + * Employs "folding", as described at + * {@link http://www.cosic.esat.kuleuven.be/publications/thesis-149.pdf#page=66 thesis-149.pdf#page=66}. To quote from + * it, "the idea [behind folding] is to find a value x' such that x (mod m) = x' (mod m), with x' being smaller than x." + * + * Unfortunately, the "Barrett Reduction with Folding" algorithm described in thesis-149.pdf is not, as written, all that + * usable on account of (1) its not using reasonable radix points as discussed in + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=162 MPM 6.2.2} and (2) the fact that, even with reasonable + * radix points, it only works when there are an even number of digits in the denominator. The reason for (2) is that + * (x >> 1) + (x >> 1) != x / 2 + x / 2. If x is even, they're the same, but if x is odd, they're not. See the in-line + * comments for details. + * + * @see _slidingWindow() + * @access private + * @param Array $n + * @param Array $m + * @return Array + */ + function _barrett($n, $m) + { + static $cache = array( + MATH_BIGINTEGER_VARIABLE => array(), + MATH_BIGINTEGER_DATA => array() + ); + + $m_length = count($m); + + // if ($this->_compare($n, $this->_square($m)) >= 0) { + if (count($n) > 2 * $m_length) { + $lhs = new Math_BigInteger(); + $rhs = new Math_BigInteger(); + $lhs->value = $n; + $rhs->value = $m; + list(, $temp) = $lhs->divide($rhs); + return $temp->value; + } + + // if (m.length >> 1) + 2 <= m.length then m is too small and n can't be reduced + if ($m_length < 5) { + return $this->_regularBarrett($n, $m); + } + + // n = 2 * m.length + + if ( ($key = array_search($m, $cache[MATH_BIGINTEGER_VARIABLE])) === false ) { + $key = count($cache[MATH_BIGINTEGER_VARIABLE]); + $cache[MATH_BIGINTEGER_VARIABLE][] = $m; + + $lhs = new Math_BigInteger(); + $lhs_value = &$lhs->value; + $lhs_value = $this->_array_repeat(0, $m_length + ($m_length >> 1)); + $lhs_value[] = 1; + $rhs = new Math_BigInteger(); + $rhs->value = $m; + + list($u, $m1) = $lhs->divide($rhs); + $u = $u->value; + $m1 = $m1->value; + + $cache[MATH_BIGINTEGER_DATA][] = array( + 'u' => $u, // m.length >> 1 (technically (m.length >> 1) + 1) + 'm1'=> $m1 // m.length + ); + } else { + extract($cache[MATH_BIGINTEGER_DATA][$key]); + } + + $cutoff = $m_length + ($m_length >> 1); + $lsd = array_slice($n, 0, $cutoff); // m.length + (m.length >> 1) + $msd = array_slice($n, $cutoff); // m.length >> 1 + $lsd = $this->_trim($lsd); + $temp = $this->_multiply($msd, false, $m1, false); + $n = $this->_add($lsd, false, $temp[MATH_BIGINTEGER_VALUE], false); // m.length + (m.length >> 1) + 1 + + if ($m_length & 1) { + return $this->_regularBarrett($n[MATH_BIGINTEGER_VALUE], $m); + } + + // (m.length + (m.length >> 1) + 1) - (m.length - 1) == (m.length >> 1) + 2 + $temp = array_slice($n[MATH_BIGINTEGER_VALUE], $m_length - 1); + // if even: ((m.length >> 1) + 2) + (m.length >> 1) == m.length + 2 + // if odd: ((m.length >> 1) + 2) + (m.length >> 1) == (m.length - 1) + 2 == m.length + 1 + $temp = $this->_multiply($temp, false, $u, false); + // if even: (m.length + 2) - ((m.length >> 1) + 1) = m.length - (m.length >> 1) + 1 + // if odd: (m.length + 1) - ((m.length >> 1) + 1) = m.length - (m.length >> 1) + $temp = array_slice($temp[MATH_BIGINTEGER_VALUE], ($m_length >> 1) + 1); + // if even: (m.length - (m.length >> 1) + 1) + m.length = 2 * m.length - (m.length >> 1) + 1 + // if odd: (m.length - (m.length >> 1)) + m.length = 2 * m.length - (m.length >> 1) + $temp = $this->_multiply($temp, false, $m, false); + + // at this point, if m had an odd number of digits, we'd be subtracting a 2 * m.length - (m.length >> 1) digit + // number from a m.length + (m.length >> 1) + 1 digit number. ie. there'd be an extra digit and the while loop + // following this comment would loop a lot (hence our calling _regularBarrett() in that situation). + + $result = $this->_subtract($n[MATH_BIGINTEGER_VALUE], false, $temp[MATH_BIGINTEGER_VALUE], false); + + while ($this->_compare($result[MATH_BIGINTEGER_VALUE], $result[MATH_BIGINTEGER_SIGN], $m, false) >= 0) { + $result = $this->_subtract($result[MATH_BIGINTEGER_VALUE], $result[MATH_BIGINTEGER_SIGN], $m, false); + } + + return $result[MATH_BIGINTEGER_VALUE]; + } + + /** + * (Regular) Barrett Modular Reduction + * + * For numbers with more than four digits Math_BigInteger::_barrett() is faster. The difference between that and this + * is that this function does not fold the denominator into a smaller form. + * + * @see _slidingWindow() + * @access private + * @param Array $x + * @param Array $n + * @return Array + */ + function _regularBarrett($x, $n) + { + static $cache = array( + MATH_BIGINTEGER_VARIABLE => array(), + MATH_BIGINTEGER_DATA => array() + ); + + $n_length = count($n); + + if (count($x) > 2 * $n_length) { + $lhs = new Math_BigInteger(); + $rhs = new Math_BigInteger(); + $lhs->value = $x; + $rhs->value = $n; + list(, $temp) = $lhs->divide($rhs); + return $temp->value; + } + + if ( ($key = array_search($n, $cache[MATH_BIGINTEGER_VARIABLE])) === false ) { + $key = count($cache[MATH_BIGINTEGER_VARIABLE]); + $cache[MATH_BIGINTEGER_VARIABLE][] = $n; + $lhs = new Math_BigInteger(); + $lhs_value = &$lhs->value; + $lhs_value = $this->_array_repeat(0, 2 * $n_length); + $lhs_value[] = 1; + $rhs = new Math_BigInteger(); + $rhs->value = $n; + list($temp, ) = $lhs->divide($rhs); // m.length + $cache[MATH_BIGINTEGER_DATA][] = $temp->value; + } + + // 2 * m.length - (m.length - 1) = m.length + 1 + $temp = array_slice($x, $n_length - 1); + // (m.length + 1) + m.length = 2 * m.length + 1 + $temp = $this->_multiply($temp, false, $cache[MATH_BIGINTEGER_DATA][$key], false); + // (2 * m.length + 1) - (m.length - 1) = m.length + 2 + $temp = array_slice($temp[MATH_BIGINTEGER_VALUE], $n_length + 1); + + // m.length + 1 + $result = array_slice($x, 0, $n_length + 1); + // m.length + 1 + $temp = $this->_multiplyLower($temp, false, $n, false, $n_length + 1); + // $temp == array_slice($temp->_multiply($temp, false, $n, false)->value, 0, $n_length + 1) + + if ($this->_compare($result, false, $temp[MATH_BIGINTEGER_VALUE], $temp[MATH_BIGINTEGER_SIGN]) < 0) { + $corrector_value = $this->_array_repeat(0, $n_length + 1); + $corrector_value[] = 1; + $result = $this->_add($result, false, $corrector, false); + $result = $result[MATH_BIGINTEGER_VALUE]; + } + + // at this point, we're subtracting a number with m.length + 1 digits from another number with m.length + 1 digits + $result = $this->_subtract($result, false, $temp[MATH_BIGINTEGER_VALUE], $temp[MATH_BIGINTEGER_SIGN]); + while ($this->_compare($result[MATH_BIGINTEGER_VALUE], $result[MATH_BIGINTEGER_SIGN], $n, false) > 0) { + $result = $this->_subtract($result[MATH_BIGINTEGER_VALUE], $result[MATH_BIGINTEGER_SIGN], $n, false); + } + + return $result[MATH_BIGINTEGER_VALUE]; + } + + /** + * Performs long multiplication up to $stop digits + * + * If you're going to be doing array_slice($product->value, 0, $stop), some cycles can be saved. + * + * @see _regularBarrett() + * @param Array $x_value + * @param Boolean $x_negative + * @param Array $y_value + * @param Boolean $y_negative + * @return Array + * @access private + */ + function _multiplyLower($x_value, $x_negative, $y_value, $y_negative, $stop) + { + $x_length = count($x_value); + $y_length = count($y_value); + + if ( !$x_length || !$y_length ) { // a 0 is being multiplied + return array( + MATH_BIGINTEGER_VALUE => array(), + MATH_BIGINTEGER_SIGN => false + ); + } + + if ( $x_length < $y_length ) { + $temp = $x_value; + $x_value = $y_value; + $y_value = $temp; + + $x_length = count($x_value); + $y_length = count($y_value); + } + + $product_value = $this->_array_repeat(0, $x_length + $y_length); + + // the following for loop could be removed if the for loop following it + // (the one with nested for loops) initially set $i to 0, but + // doing so would also make the result in one set of unnecessary adds, + // since on the outermost loops first pass, $product->value[$k] is going + // to always be 0 + + $carry = 0; + + for ($j = 0; $j < $x_length; ++$j) { // ie. $i = 0, $k = $i + $temp = $x_value[$j] * $y_value[0] + $carry; // $product_value[$k] == 0 + $carry = (int) ($temp / 0x4000000); + $product_value[$j] = (int) ($temp - 0x4000000 * $carry); + } + + if ($j < $stop) { + $product_value[$j] = $carry; + } + + // the above for loop is what the previous comment was talking about. the + // following for loop is the "one with nested for loops" + + for ($i = 1; $i < $y_length; ++$i) { + $carry = 0; + + for ($j = 0, $k = $i; $j < $x_length && $k < $stop; ++$j, ++$k) { + $temp = $product_value[$k] + $x_value[$j] * $y_value[$i] + $carry; + $carry = (int) ($temp / 0x4000000); + $product_value[$k] = (int) ($temp - 0x4000000 * $carry); + } + + if ($k < $stop) { + $product_value[$k] = $carry; + } + } + + return array( + MATH_BIGINTEGER_VALUE => $this->_trim($product_value), + MATH_BIGINTEGER_SIGN => $x_negative != $y_negative + ); + } + + /** + * Montgomery Modular Reduction + * + * ($x->_prepMontgomery($n))->_montgomery($n) yields $x % $n. + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=170 MPM 6.3} provides insights on how this can be + * improved upon (basically, by using the comba method). gcd($n, 2) must be equal to one for this function + * to work correctly. + * + * @see _prepMontgomery() + * @see _slidingWindow() + * @access private + * @param Array $x + * @param Array $n + * @return Array + */ + function _montgomery($x, $n) + { + static $cache = array( + MATH_BIGINTEGER_VARIABLE => array(), + MATH_BIGINTEGER_DATA => array() + ); + + if ( ($key = array_search($n, $cache[MATH_BIGINTEGER_VARIABLE])) === false ) { + $key = count($cache[MATH_BIGINTEGER_VARIABLE]); + $cache[MATH_BIGINTEGER_VARIABLE][] = $x; + $cache[MATH_BIGINTEGER_DATA][] = $this->_modInverse67108864($n); + } + + $k = count($n); + + $result = array(MATH_BIGINTEGER_VALUE => $x); + + for ($i = 0; $i < $k; ++$i) { + $temp = $result[MATH_BIGINTEGER_VALUE][$i] * $cache[MATH_BIGINTEGER_DATA][$key]; + $temp = (int) ($temp - 0x4000000 * ((int) ($temp / 0x4000000))); + $temp = $this->_regularMultiply(array($temp), $n); + $temp = array_merge($this->_array_repeat(0, $i), $temp); + $result = $this->_add($result[MATH_BIGINTEGER_VALUE], false, $temp, false); + } + + $result[MATH_BIGINTEGER_VALUE] = array_slice($result[MATH_BIGINTEGER_VALUE], $k); + + if ($this->_compare($result, false, $n, false) >= 0) { + $result = $this->_subtract($result[MATH_BIGINTEGER_VALUE], false, $n, false); + } + + return $result[MATH_BIGINTEGER_VALUE]; + } + + /** + * Montgomery Multiply + * + * Interleaves the montgomery reduction and long multiplication algorithms together as described in + * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=13 HAC 14.36} + * + * @see _prepMontgomery() + * @see _montgomery() + * @access private + * @param Array $x + * @param Array $y + * @param Array $m + * @return Array + */ + function _montgomeryMultiply($x, $y, $m) + { + $temp = $this->_multiply($x, false, $y, false); + return $this->_montgomery($temp[MATH_BIGINTEGER_VALUE], $m); + + static $cache = array( + MATH_BIGINTEGER_VARIABLE => array(), + MATH_BIGINTEGER_DATA => array() + ); + + if ( ($key = array_search($m, $cache[MATH_BIGINTEGER_VARIABLE])) === false ) { + $key = count($cache[MATH_BIGINTEGER_VARIABLE]); + $cache[MATH_BIGINTEGER_VARIABLE][] = $m; + $cache[MATH_BIGINTEGER_DATA][] = $this->_modInverse67108864($m); + } + + $n = max(count($x), count($y), count($m)); + $x = array_pad($x, $n, 0); + $y = array_pad($y, $n, 0); + $m = array_pad($m, $n, 0); + $a = array(MATH_BIGINTEGER_VALUE => $this->_array_repeat(0, $n + 1)); + for ($i = 0; $i < $n; ++$i) { + $temp = $a[MATH_BIGINTEGER_VALUE][0] + $x[$i] * $y[0]; + $temp = (int) ($temp - 0x4000000 * ((int) ($temp / 0x4000000))); + $temp = $temp * $cache[MATH_BIGINTEGER_DATA][$key]; + $temp = (int) ($temp - 0x4000000 * ((int) ($temp / 0x4000000))); + $temp = $this->_add($this->_regularMultiply(array($x[$i]), $y), false, $this->_regularMultiply(array($temp), $m), false); + $a = $this->_add($a[MATH_BIGINTEGER_VALUE], false, $temp[MATH_BIGINTEGER_VALUE], false); + $a[MATH_BIGINTEGER_VALUE] = array_slice($a[MATH_BIGINTEGER_VALUE], 1); + } + if ($this->_compare($a[MATH_BIGINTEGER_VALUE], false, $m, false) >= 0) { + $a = $this->_subtract($a[MATH_BIGINTEGER_VALUE], false, $m, false); + } + return $a[MATH_BIGINTEGER_VALUE]; + } + + /** + * Prepare a number for use in Montgomery Modular Reductions + * + * @see _montgomery() + * @see _slidingWindow() + * @access private + * @param Array $x + * @param Array $n + * @return Array + */ + function _prepMontgomery($x, $n) + { + $lhs = new Math_BigInteger(); + $lhs->value = array_merge($this->_array_repeat(0, count($n)), $x); + $rhs = new Math_BigInteger(); + $rhs->value = $n; + + list(, $temp) = $lhs->divide($rhs); + return $temp->value; + } + + /** + * Modular Inverse of a number mod 2**26 (eg. 67108864) + * + * Based off of the bnpInvDigit function implemented and justified in the following URL: + * + * {@link http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn.js} + * + * The following URL provides more info: + * + * {@link http://groups.google.com/group/sci.crypt/msg/7a137205c1be7d85} + * + * As for why we do all the bitmasking... strange things can happen when converting from floats to ints. For + * instance, on some computers, var_dump((int) -4294967297) yields int(-1) and on others, it yields + * int(-2147483648). To avoid problems stemming from this, we use bitmasks to guarantee that ints aren't + * auto-converted to floats. The outermost bitmask is present because without it, there's no guarantee that + * the "residue" returned would be the so-called "common residue". We use fmod, in the last step, because the + * maximum possible $x is 26 bits and the maximum $result is 16 bits. Thus, we have to be able to handle up to + * 40 bits, which only 64-bit floating points will support. + * + * Thanks to Pedro Gimeno Fortea for input! + * + * @see _montgomery() + * @access private + * @param Array $x + * @return Integer + */ + function _modInverse67108864($x) // 2**26 == 67108864 + { + $x = -$x[0]; + $result = $x & 0x3; // x**-1 mod 2**2 + $result = ($result * (2 - $x * $result)) & 0xF; // x**-1 mod 2**4 + $result = ($result * (2 - ($x & 0xFF) * $result)) & 0xFF; // x**-1 mod 2**8 + $result = ($result * ((2 - ($x & 0xFFFF) * $result) & 0xFFFF)) & 0xFFFF; // x**-1 mod 2**16 + $result = fmod($result * (2 - fmod($x * $result, 0x4000000)), 0x4000000); // x**-1 mod 2**26 + return $result & 0x3FFFFFF; + } + + /** + * Calculates modular inverses. + * + * Say you have (30 mod 17 * x mod 17) mod 17 == 1. x can be found using modular inverses. + * + * Here's an example: + * + * modInverse($b); + * echo $c->toString(); // outputs 4 + * + * echo "\r\n"; + * + * $d = $a->multiply($c); + * list(, $d) = $d->divide($b); + * echo $d; // outputs 1 (as per the definition of modular inverse) + * ?> + * + * + * @param Math_BigInteger $n + * @return mixed false, if no modular inverse exists, Math_BigInteger, otherwise. + * @access public + * @internal See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=21 HAC 14.64} for more information. + */ + function modInverse($n) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_invert($this->value, $n->value); + + return ( $temp->value === false ) ? false : $this->_normalize($temp); + } + + static $zero, $one; + if (!isset($zero)) { + $zero = new Math_BigInteger(); + $one = new Math_BigInteger(1); + } + + // $x mod $n == $x mod -$n. + $n = $n->abs(); + + if ($this->compare($zero) < 0) { + $temp = $this->abs(); + $temp = $temp->modInverse($n); + return $negated === false ? false : $this->_normalize($n->subtract($temp)); + } + + extract($this->extendedGCD($n)); + + if (!$gcd->equals($one)) { + return false; + } + + $x = $x->compare($zero) < 0 ? $x->add($n) : $x; + + return $this->compare($zero) < 0 ? $this->_normalize($n->subtract($x)) : $this->_normalize($x); + } + + /** + * Calculates the greatest common divisor and Bézout's identity. + * + * Say you have 693 and 609. The GCD is 21. Bézout's identity states that there exist integers x and y such that + * 693*x + 609*y == 21. In point of fact, there are actually an infinite number of x and y combinations and which + * combination is returned is dependant upon which mode is in use. See + * {@link http://en.wikipedia.org/wiki/B%C3%A9zout%27s_identity Bézout's identity - Wikipedia} for more information. + * + * Here's an example: + * + * extendedGCD($b)); + * + * echo $gcd->toString() . "\r\n"; // outputs 21 + * echo $a->toString() * $x->toString() + $b->toString() * $y->toString(); // outputs 21 + * ?> + * + * + * @param Math_BigInteger $n + * @return Math_BigInteger + * @access public + * @internal Calculates the GCD using the binary xGCD algorithim described in + * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=19 HAC 14.61}. As the text above 14.61 notes, + * the more traditional algorithim requires "relatively costly multiple-precision divisions". + */ + function extendedGCD($n) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + extract(gmp_gcdext($this->value, $n->value)); + + return array( + 'gcd' => $this->_normalize(new Math_BigInteger($g)), + 'x' => $this->_normalize(new Math_BigInteger($s)), + 'y' => $this->_normalize(new Math_BigInteger($t)) + ); + case MATH_BIGINTEGER_MODE_BCMATH: + // it might be faster to use the binary xGCD algorithim here, as well, but (1) that algorithim works + // best when the base is a power of 2 and (2) i don't think it'd make much difference, anyway. as is, + // the basic extended euclidean algorithim is what we're using. + + $u = $this->value; + $v = $n->value; + + $a = '1'; + $b = '0'; + $c = '0'; + $d = '1'; + + while (bccomp($v, '0', 0) != 0) { + $q = bcdiv($u, $v, 0); + + $temp = $u; + $u = $v; + $v = bcsub($temp, bcmul($v, $q, 0), 0); + + $temp = $a; + $a = $c; + $c = bcsub($temp, bcmul($a, $q, 0), 0); + + $temp = $b; + $b = $d; + $d = bcsub($temp, bcmul($b, $q, 0), 0); + } + + return array( + 'gcd' => $this->_normalize(new Math_BigInteger($u)), + 'x' => $this->_normalize(new Math_BigInteger($a)), + 'y' => $this->_normalize(new Math_BigInteger($b)) + ); + } + + $y = $n->copy(); + $x = $this->copy(); + $g = new Math_BigInteger(); + $g->value = array(1); + + while ( !(($x->value[0] & 1)|| ($y->value[0] & 1)) ) { + $x->_rshift(1); + $y->_rshift(1); + $g->_lshift(1); + } + + $u = $x->copy(); + $v = $y->copy(); + + $a = new Math_BigInteger(); + $b = new Math_BigInteger(); + $c = new Math_BigInteger(); + $d = new Math_BigInteger(); + + $a->value = $d->value = $g->value = array(1); + $b->value = $c->value = array(); + + while ( !empty($u->value) ) { + while ( !($u->value[0] & 1) ) { + $u->_rshift(1); + if ( (!empty($a->value) && ($a->value[0] & 1)) || (!empty($b->value) && ($b->value[0] & 1)) ) { + $a = $a->add($y); + $b = $b->subtract($x); + } + $a->_rshift(1); + $b->_rshift(1); + } + + while ( !($v->value[0] & 1) ) { + $v->_rshift(1); + if ( (!empty($d->value) && ($d->value[0] & 1)) || (!empty($c->value) && ($c->value[0] & 1)) ) { + $c = $c->add($y); + $d = $d->subtract($x); + } + $c->_rshift(1); + $d->_rshift(1); + } + + if ($u->compare($v) >= 0) { + $u = $u->subtract($v); + $a = $a->subtract($c); + $b = $b->subtract($d); + } else { + $v = $v->subtract($u); + $c = $c->subtract($a); + $d = $d->subtract($b); + } + } + + return array( + 'gcd' => $this->_normalize($g->multiply($v)), + 'x' => $this->_normalize($c), + 'y' => $this->_normalize($d) + ); + } + + /** + * Calculates the greatest common divisor + * + * Say you have 693 and 609. The GCD is 21. + * + * Here's an example: + * + * extendedGCD($b); + * + * echo $gcd->toString() . "\r\n"; // outputs 21 + * ?> + * + * + * @param Math_BigInteger $n + * @return Math_BigInteger + * @access public + */ + function gcd($n) + { + extract($this->extendedGCD($n)); + return $gcd; + } + + /** + * Absolute value. + * + * @return Math_BigInteger + * @access public + */ + function abs() + { + $temp = new Math_BigInteger(); + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp->value = gmp_abs($this->value); + break; + case MATH_BIGINTEGER_MODE_BCMATH: + $temp->value = (bccomp($this->value, '0', 0) < 0) ? substr($this->value, 1) : $this->value; + break; + default: + $temp->value = $this->value; + } + + return $temp; + } + + /** + * Compares two numbers. + * + * Although one might think !$x->compare($y) means $x != $y, it, in fact, means the opposite. The reason for this is + * demonstrated thusly: + * + * $x > $y: $x->compare($y) > 0 + * $x < $y: $x->compare($y) < 0 + * $x == $y: $x->compare($y) == 0 + * + * Note how the same comparison operator is used. If you want to test for equality, use $x->equals($y). + * + * @param Math_BigInteger $x + * @return Integer < 0 if $this is less than $x; > 0 if $this is greater than $x, and 0 if they are equal. + * @access public + * @see equals() + * @internal Could return $this->subtract($x), but that's not as fast as what we do do. + */ + function compare($y) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + return gmp_cmp($this->value, $y->value); + case MATH_BIGINTEGER_MODE_BCMATH: + return bccomp($this->value, $y->value, 0); + } + + return $this->_compare($this->value, $this->is_negative, $y->value, $y->is_negative); + } + + /** + * Compares two numbers. + * + * @param Array $x_value + * @param Boolean $x_negative + * @param Array $y_value + * @param Boolean $y_negative + * @return Integer + * @see compare() + * @access private + */ + function _compare($x_value, $x_negative, $y_value, $y_negative) + { + if ( $x_negative != $y_negative ) { + return ( !$x_negative && $y_negative ) ? 1 : -1; + } + + $result = $x_negative ? -1 : 1; + + if ( count($x_value) != count($y_value) ) { + return ( count($x_value) > count($y_value) ) ? $result : -$result; + } + $size = max(count($x_value), count($y_value)); + + $x_value = array_pad($x_value, $size, 0); + $y_value = array_pad($y_value, $size, 0); + + for ($i = count($x_value) - 1; $i >= 0; --$i) { + if ($x_value[$i] != $y_value[$i]) { + return ( $x_value[$i] > $y_value[$i] ) ? $result : -$result; + } + } + + return 0; + } + + /** + * Tests the equality of two numbers. + * + * If you need to see if one number is greater than or less than another number, use Math_BigInteger::compare() + * + * @param Math_BigInteger $x + * @return Boolean + * @access public + * @see compare() + */ + function equals($x) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + return gmp_cmp($this->value, $x->value) == 0; + default: + return $this->value === $x->value && $this->is_negative == $x->is_negative; + } + } + + /** + * Set Precision + * + * Some bitwise operations give different results depending on the precision being used. Examples include left + * shift, not, and rotates. + * + * @param Math_BigInteger $x + * @access public + * @return Math_BigInteger + */ + function setPrecision($bits) + { + $this->precision = $bits; + if ( MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_BCMATH ) { + $this->bitmask = new Math_BigInteger(chr((1 << ($bits & 0x7)) - 1) . str_repeat(chr(0xFF), $bits >> 3), 256); + } else { + $this->bitmask = new Math_BigInteger(bcpow('2', $bits, 0)); + } + + $temp = $this->_normalize($this); + $this->value = $temp->value; + } + + /** + * Logical And + * + * @param Math_BigInteger $x + * @access public + * @internal Implemented per a request by Lluis Pamies i Juarez + * @return Math_BigInteger + */ + function bitwise_and($x) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_and($this->value, $x->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $left = $this->toBytes(); + $right = $x->toBytes(); + + $length = max(strlen($left), strlen($right)); + + $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); + $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); + + return $this->_normalize(new Math_BigInteger($left & $right, 256)); + } + + $result = $this->copy(); + + $length = min(count($x->value), count($this->value)); + + $result->value = array_slice($result->value, 0, $length); + + for ($i = 0; $i < $length; ++$i) { + $result->value[$i] = $result->value[$i] & $x->value[$i]; + } + + return $this->_normalize($result); + } + + /** + * Logical Or + * + * @param Math_BigInteger $x + * @access public + * @internal Implemented per a request by Lluis Pamies i Juarez + * @return Math_BigInteger + */ + function bitwise_or($x) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_or($this->value, $x->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $left = $this->toBytes(); + $right = $x->toBytes(); + + $length = max(strlen($left), strlen($right)); + + $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); + $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); + + return $this->_normalize(new Math_BigInteger($left | $right, 256)); + } + + $length = max(count($this->value), count($x->value)); + $result = $this->copy(); + $result->value = array_pad($result->value, 0, $length); + $x->value = array_pad($x->value, 0, $length); + + for ($i = 0; $i < $length; ++$i) { + $result->value[$i] = $this->value[$i] | $x->value[$i]; + } + + return $this->_normalize($result); + } + + /** + * Logical Exclusive-Or + * + * @param Math_BigInteger $x + * @access public + * @internal Implemented per a request by Lluis Pamies i Juarez + * @return Math_BigInteger + */ + function bitwise_xor($x) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_xor($this->value, $x->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $left = $this->toBytes(); + $right = $x->toBytes(); + + $length = max(strlen($left), strlen($right)); + + $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); + $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); + + return $this->_normalize(new Math_BigInteger($left ^ $right, 256)); + } + + $length = max(count($this->value), count($x->value)); + $result = $this->copy(); + $result->value = array_pad($result->value, 0, $length); + $x->value = array_pad($x->value, 0, $length); + + for ($i = 0; $i < $length; ++$i) { + $result->value[$i] = $this->value[$i] ^ $x->value[$i]; + } + + return $this->_normalize($result); + } + + /** + * Logical Not + * + * @access public + * @internal Implemented per a request by Lluis Pamies i Juarez + * @return Math_BigInteger + */ + function bitwise_not() + { + // calculuate "not" without regard to $this->precision + // (will always result in a smaller number. ie. ~1 isn't 1111 1110 - it's 0) + $temp = $this->toBytes(); + $pre_msb = decbin(ord($temp[0])); + $temp = ~$temp; + $msb = decbin(ord($temp[0])); + if (strlen($msb) == 8) { + $msb = substr($msb, strpos($msb, '0')); + } + $temp[0] = chr(bindec($msb)); + + // see if we need to add extra leading 1's + $current_bits = strlen($pre_msb) + 8 * strlen($temp) - 8; + $new_bits = $this->precision - $current_bits; + if ($new_bits <= 0) { + return $this->_normalize(new Math_BigInteger($temp, 256)); + } + + // generate as many leading 1's as we need to. + $leading_ones = chr((1 << ($new_bits & 0x7)) - 1) . str_repeat(chr(0xFF), $new_bits >> 3); + $this->_base256_lshift($leading_ones, $current_bits); + + $temp = str_pad($temp, ceil($this->bits / 8), chr(0), STR_PAD_LEFT); + + return $this->_normalize(new Math_BigInteger($leading_ones | $temp, 256)); + } + + /** + * Logical Right Shift + * + * Shifts BigInteger's by $shift bits, effectively dividing by 2**$shift. + * + * @param Integer $shift + * @return Math_BigInteger + * @access public + * @internal The only version that yields any speed increases is the internal version. + */ + function bitwise_rightShift($shift) + { + $temp = new Math_BigInteger(); + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + static $two; + + if (!isset($two)) { + $two = gmp_init('2'); + } + + $temp->value = gmp_div_q($this->value, gmp_pow($two, $shift)); + + break; + case MATH_BIGINTEGER_MODE_BCMATH: + $temp->value = bcdiv($this->value, bcpow('2', $shift, 0), 0); + + break; + default: // could just replace _lshift with this, but then all _lshift() calls would need to be rewritten + // and I don't want to do that... + $temp->value = $this->value; + $temp->_rshift($shift); + } + + return $this->_normalize($temp); + } + + /** + * Logical Left Shift + * + * Shifts BigInteger's by $shift bits, effectively multiplying by 2**$shift. + * + * @param Integer $shift + * @return Math_BigInteger + * @access public + * @internal The only version that yields any speed increases is the internal version. + */ + function bitwise_leftShift($shift) + { + $temp = new Math_BigInteger(); + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + static $two; + + if (!isset($two)) { + $two = gmp_init('2'); + } + + $temp->value = gmp_mul($this->value, gmp_pow($two, $shift)); + + break; + case MATH_BIGINTEGER_MODE_BCMATH: + $temp->value = bcmul($this->value, bcpow('2', $shift, 0), 0); + + break; + default: // could just replace _rshift with this, but then all _lshift() calls would need to be rewritten + // and I don't want to do that... + $temp->value = $this->value; + $temp->_lshift($shift); + } + + return $this->_normalize($temp); + } + + /** + * Logical Left Rotate + * + * Instead of the top x bits being dropped they're appended to the shifted bit string. + * + * @param Integer $shift + * @return Math_BigInteger + * @access public + */ + function bitwise_leftRotate($shift) + { + $bits = $this->toBytes(); + + if ($this->precision > 0) { + $precision = $this->precision; + if ( MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_BCMATH ) { + $mask = $this->bitmask->subtract(new Math_BigInteger(1)); + $mask = $mask->toBytes(); + } else { + $mask = $this->bitmask->toBytes(); + } + } else { + $temp = ord($bits[0]); + for ($i = 0; $temp >> $i; ++$i); + $precision = 8 * strlen($bits) - 8 + $i; + $mask = chr((1 << ($precision & 0x7)) - 1) . str_repeat(chr(0xFF), $precision >> 3); + } + + if ($shift < 0) { + $shift+= $precision; + } + $shift%= $precision; + + if (!$shift) { + return $this->copy(); + } + + $left = $this->bitwise_leftShift($shift); + $left = $left->bitwise_and(new Math_BigInteger($mask, 256)); + $right = $this->bitwise_rightShift($precision - $shift); + $result = MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_BCMATH ? $left->bitwise_or($right) : $left->add($right); + return $this->_normalize($result); + } + + /** + * Logical Right Rotate + * + * Instead of the bottom x bits being dropped they're prepended to the shifted bit string. + * + * @param Integer $shift + * @return Math_BigInteger + * @access public + */ + function bitwise_rightRotate($shift) + { + return $this->bitwise_leftRotate(-$shift); + } + + /** + * Set random number generator function + * + * $generator should be the name of a random generating function whose first parameter is the minimum + * value and whose second parameter is the maximum value. If this function needs to be seeded, it should + * be seeded prior to calling Math_BigInteger::random() or Math_BigInteger::randomPrime() + * + * If the random generating function is not explicitly set, it'll be assumed to be mt_rand(). + * + * @see random() + * @see randomPrime() + * @param optional String $generator + * @access public + */ + function setRandomGenerator($generator) + { + $this->generator = $generator; + } + + /** + * Generate a random number + * + * @param optional Integer $min + * @param optional Integer $max + * @return Math_BigInteger + * @access public + */ + function random($min = false, $max = false) + { + if ($min === false) { + $min = new Math_BigInteger(0); + } + + if ($max === false) { + $max = new Math_BigInteger(0x7FFFFFFF); + } + + $compare = $max->compare($min); + + if (!$compare) { + return $this->_normalize($min); + } else if ($compare < 0) { + // if $min is bigger then $max, swap $min and $max + $temp = $max; + $max = $min; + $min = $temp; + } + + $generator = $this->generator; + + $max = $max->subtract($min); + $max = ltrim($max->toBytes(), chr(0)); + $size = strlen($max) - 1; + $random = ''; + + $bytes = $size & 1; + for ($i = 0; $i < $bytes; ++$i) { + $random.= chr($generator(0, 255)); + } + + $blocks = $size >> 1; + for ($i = 0; $i < $blocks; ++$i) { + // mt_rand(-2147483648, 0x7FFFFFFF) always produces -2147483648 on some systems + $random.= pack('n', $generator(0, 0xFFFF)); + } + + $temp = new Math_BigInteger($random, 256); + if ($temp->compare(new Math_BigInteger(substr($max, 1), 256)) > 0) { + $random = chr($generator(0, ord($max[0]) - 1)) . $random; + } else { + $random = chr($generator(0, ord($max[0]) )) . $random; + } + + $random = new Math_BigInteger($random, 256); + + return $this->_normalize($random->add($min)); + } + + /** + * Generate a random prime number. + * + * If there's not a prime within the given range, false will be returned. If more than $timeout seconds have elapsed, + * give up and return false. + * + * @param optional Integer $min + * @param optional Integer $max + * @param optional Integer $timeout + * @return Math_BigInteger + * @access public + * @internal See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap4.pdf#page=15 HAC 4.44}. + */ + function randomPrime($min = false, $max = false, $timeout = false) + { + $compare = $max->compare($min); + + if (!$compare) { + return $min; + } else if ($compare < 0) { + // if $min is bigger then $max, swap $min and $max + $temp = $max; + $max = $min; + $min = $temp; + } + + // gmp_nextprime() requires PHP 5 >= 5.2.0 per . + if ( MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_GMP && function_exists('gmp_nextprime') ) { + // we don't rely on Math_BigInteger::random()'s min / max when gmp_nextprime() is being used since this function + // does its own checks on $max / $min when gmp_nextprime() is used. When gmp_nextprime() is not used, however, + // the same $max / $min checks are not performed. + if ($min === false) { + $min = new Math_BigInteger(0); + } + + if ($max === false) { + $max = new Math_BigInteger(0x7FFFFFFF); + } + + $x = $this->random($min, $max); + + $x->value = gmp_nextprime($x->value); + + if ($x->compare($max) <= 0) { + return $x; + } + + $x->value = gmp_nextprime($min->value); + + if ($x->compare($max) <= 0) { + return $x; + } + + return false; + } + + static $one, $two; + if (!isset($one)) { + $one = new Math_BigInteger(1); + $two = new Math_BigInteger(2); + } + + $start = time(); + + $x = $this->random($min, $max); + if ($x->equals($two)) { + return $x; + } + + $x->_make_odd(); + if ($x->compare($max) > 0) { + // if $x > $max then $max is even and if $min == $max then no prime number exists between the specified range + if ($min->equals($max)) { + return false; + } + $x = $min->copy(); + $x->_make_odd(); + } + + $initial_x = $x->copy(); + + while (true) { + if ($timeout !== false && time() - $start > $timeout) { + return false; + } + + if ($x->isPrime()) { + return $x; + } + + $x = $x->add($two); + + if ($x->compare($max) > 0) { + $x = $min->copy(); + if ($x->equals($two)) { + return $x; + } + $x->_make_odd(); + } + + if ($x->equals($initial_x)) { + return false; + } + } + } + + /** + * Make the current number odd + * + * If the current number is odd it'll be unchanged. If it's even, one will be added to it. + * + * @see randomPrime() + * @access private + */ + function _make_odd() + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + gmp_setbit($this->value, 0); + break; + case MATH_BIGINTEGER_MODE_BCMATH: + if ($this->value[strlen($this->value) - 1] % 2 == 0) { + $this->value = bcadd($this->value, '1'); + } + break; + default: + $this->value[0] |= 1; + } + } + + /** + * Checks a numer to see if it's prime + * + * Assuming the $t parameter is not set, this function has an error rate of 2**-80. The main motivation for the + * $t parameter is distributability. Math_BigInteger::randomPrime() can be distributed accross multiple pageloads + * on a website instead of just one. + * + * @param optional Integer $t + * @return Boolean + * @access public + * @internal Uses the + * {@link http://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test Miller-Rabin primality test}. See + * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap4.pdf#page=8 HAC 4.24}. + */ + function isPrime($t = false) + { + $length = strlen($this->toBytes()); + + if (!$t) { + // see HAC 4.49 "Note (controlling the error probability)" + if ($length >= 163) { $t = 2; } // floor(1300 / 8) + else if ($length >= 106) { $t = 3; } // floor( 850 / 8) + else if ($length >= 81 ) { $t = 4; } // floor( 650 / 8) + else if ($length >= 68 ) { $t = 5; } // floor( 550 / 8) + else if ($length >= 56 ) { $t = 6; } // floor( 450 / 8) + else if ($length >= 50 ) { $t = 7; } // floor( 400 / 8) + else if ($length >= 43 ) { $t = 8; } // floor( 350 / 8) + else if ($length >= 37 ) { $t = 9; } // floor( 300 / 8) + else if ($length >= 31 ) { $t = 12; } // floor( 250 / 8) + else if ($length >= 25 ) { $t = 15; } // floor( 200 / 8) + else if ($length >= 18 ) { $t = 18; } // floor( 150 / 8) + else { $t = 27; } + } + + // ie. gmp_testbit($this, 0) + // ie. isEven() or !isOdd() + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + return gmp_prob_prime($this->value, $t) != 0; + case MATH_BIGINTEGER_MODE_BCMATH: + if ($this->value === '2') { + return true; + } + if ($this->value[strlen($this->value) - 1] % 2 == 0) { + return false; + } + break; + default: + if ($this->value == array(2)) { + return true; + } + if (~$this->value[0] & 1) { + return false; + } + } + + static $primes, $zero, $one, $two; + + if (!isset($primes)) { + $primes = array( + 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, + 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, + 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, + 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, + 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, + 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, + 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, + 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, + 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, + 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, + 953, 967, 971, 977, 983, 991, 997 + ); + + if ( MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_INTERNAL ) { + for ($i = 0; $i < count($primes); ++$i) { + $primes[$i] = new Math_BigInteger($primes[$i]); + } + } + + $zero = new Math_BigInteger(); + $one = new Math_BigInteger(1); + $two = new Math_BigInteger(2); + } + + if ($this->equals($one)) { + return false; + } + + // see HAC 4.4.1 "Random search for probable primes" + if ( MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_INTERNAL ) { + foreach ($primes as $prime) { + list(, $r) = $this->divide($prime); + if ($r->equals($zero)) { + return $this->equals($prime); + } + } + } else { + $value = $this->value; + foreach ($primes as $prime) { + list(, $r) = $this->_divide_digit($value, $prime); + if (!$r) { + return count($value) == 1 && $value[0] == $prime; + } + } + } + + $n = $this->copy(); + $n_1 = $n->subtract($one); + $n_2 = $n->subtract($two); + + $r = $n_1->copy(); + $r_value = $r->value; + // ie. $s = gmp_scan1($n, 0) and $r = gmp_div_q($n, gmp_pow(gmp_init('2'), $s)); + if ( MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_BCMATH ) { + $s = 0; + // if $n was 1, $r would be 0 and this would be an infinite loop, hence our $this->equals($one) check earlier + while ($r->value[strlen($r->value) - 1] % 2 == 0) { + $r->value = bcdiv($r->value, '2', 0); + ++$s; + } + } else { + for ($i = 0, $r_length = count($r_value); $i < $r_length; ++$i) { + $temp = ~$r_value[$i] & 0xFFFFFF; + for ($j = 1; ($temp >> $j) & 1; ++$j); + if ($j != 25) { + break; + } + } + $s = 26 * $i + $j - 1; + $r->_rshift($s); + } + + for ($i = 0; $i < $t; ++$i) { + $a = $this->random($two, $n_2); + $y = $a->modPow($r, $n); + + if (!$y->equals($one) && !$y->equals($n_1)) { + for ($j = 1; $j < $s && !$y->equals($n_1); ++$j) { + $y = $y->modPow($two, $n); + if ($y->equals($one)) { + return false; + } + } + + if (!$y->equals($n_1)) { + return false; + } + } + } + return true; + } + + /** + * Logical Left Shift + * + * Shifts BigInteger's by $shift bits. + * + * @param Integer $shift + * @access private + */ + function _lshift($shift) + { + if ( $shift == 0 ) { + return; + } + + $num_digits = (int) ($shift / 26); + $shift %= 26; + $shift = 1 << $shift; + + $carry = 0; + + for ($i = 0; $i < count($this->value); ++$i) { + $temp = $this->value[$i] * $shift + $carry; + $carry = (int) ($temp / 0x4000000); + $this->value[$i] = (int) ($temp - $carry * 0x4000000); + } + + if ( $carry ) { + $this->value[] = $carry; + } + + while ($num_digits--) { + array_unshift($this->value, 0); + } + } + + /** + * Logical Right Shift + * + * Shifts BigInteger's by $shift bits. + * + * @param Integer $shift + * @access private + */ + function _rshift($shift) + { + if ($shift == 0) { + return; + } + + $num_digits = (int) ($shift / 26); + $shift %= 26; + $carry_shift = 26 - $shift; + $carry_mask = (1 << $shift) - 1; + + if ( $num_digits ) { + $this->value = array_slice($this->value, $num_digits); + } + + $carry = 0; + + for ($i = count($this->value) - 1; $i >= 0; --$i) { + $temp = $this->value[$i] >> $shift | $carry; + $carry = ($this->value[$i] & $carry_mask) << $carry_shift; + $this->value[$i] = $temp; + } + + $this->value = $this->_trim($this->value); + } + + /** + * Normalize + * + * Removes leading zeros and truncates (if necessary) to maintain the appropriate precision + * + * @param Math_BigInteger + * @return Math_BigInteger + * @see _trim() + * @access private + */ + function _normalize($result) + { + $result->precision = $this->precision; + $result->bitmask = $this->bitmask; + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + if (!empty($result->bitmask->value)) { + $result->value = gmp_and($result->value, $result->bitmask->value); + } + + return $result; + case MATH_BIGINTEGER_MODE_BCMATH: + if (!empty($result->bitmask->value)) { + $result->value = bcmod($result->value, $result->bitmask->value); + } + + return $result; + } + + $value = &$result->value; + + if ( !count($value) ) { + return $result; + } + + $value = $this->_trim($value); + + if (!empty($result->bitmask->value)) { + $length = min(count($value), count($this->bitmask->value)); + $value = array_slice($value, 0, $length); + + for ($i = 0; $i < $length; ++$i) { + $value[$i] = $value[$i] & $this->bitmask->value[$i]; + } + } + + return $result; + } + + /** + * Trim + * + * Removes leading zeros + * + * @return Math_BigInteger + * @access private + */ + function _trim($value) + { + for ($i = count($value) - 1; $i >= 0; --$i) { + if ( $value[$i] ) { + break; + } + unset($value[$i]); + } + + return $value; + } + + /** + * Array Repeat + * + * @param $input Array + * @param $multiplier mixed + * @return Array + * @access private + */ + function _array_repeat($input, $multiplier) + { + return ($multiplier) ? array_fill(0, $multiplier, $input) : array(); + } + + /** + * Logical Left Shift + * + * Shifts binary strings $shift bits, essentially multiplying by 2**$shift. + * + * @param $x String + * @param $shift Integer + * @return String + * @access private + */ + function _base256_lshift(&$x, $shift) + { + if ($shift == 0) { + return; + } + + $num_bytes = $shift >> 3; // eg. floor($shift/8) + $shift &= 7; // eg. $shift % 8 + + $carry = 0; + for ($i = strlen($x) - 1; $i >= 0; --$i) { + $temp = ord($x[$i]) << $shift | $carry; + $x[$i] = chr($temp); + $carry = $temp >> 8; + } + $carry = ($carry != 0) ? chr($carry) : ''; + $x = $carry . $x . str_repeat(chr(0), $num_bytes); + } + + /** + * Logical Right Shift + * + * Shifts binary strings $shift bits, essentially dividing by 2**$shift and returning the remainder. + * + * @param $x String + * @param $shift Integer + * @return String + * @access private + */ + function _base256_rshift(&$x, $shift) + { + if ($shift == 0) { + $x = ltrim($x, chr(0)); + return ''; + } + + $num_bytes = $shift >> 3; // eg. floor($shift/8) + $shift &= 7; // eg. $shift % 8 + + $remainder = ''; + if ($num_bytes) { + $start = $num_bytes > strlen($x) ? -strlen($x) : -$num_bytes; + $remainder = substr($x, $start); + $x = substr($x, 0, -$num_bytes); + } + + $carry = 0; + $carry_shift = 8 - $shift; + for ($i = 0; $i < strlen($x); ++$i) { + $temp = (ord($x[$i]) >> $shift) | $carry; + $carry = (ord($x[$i]) << $carry_shift) & 0xFF; + $x[$i] = chr($temp); + } + $x = ltrim($x, chr(0)); + + $remainder = chr($carry >> $carry_shift) . $remainder; + + return ltrim($remainder, chr(0)); + } + + // one quirk about how the following functions are implemented is that PHP defines N to be an unsigned long + // at 32-bits, while java's longs are 64-bits. + + /** + * Converts 32-bit integers to bytes. + * + * @param Integer $x + * @return String + * @access private + */ + function _int2bytes($x) + { + return ltrim(pack('N', $x), chr(0)); + } + + /** + * Converts bytes to 32-bit integers + * + * @param String $x + * @return Integer + * @access private + */ + function _bytes2int($x) + { + $temp = unpack('Nint', str_pad($x, 4, chr(0), STR_PAD_LEFT)); + return $temp['int']; + } } \ No newline at end of file -- cgit v1.2.3-54-g00ecf From 4e44cf906bb4fc884f645388c2c90ca1bad9a88f Mon Sep 17 00:00:00 2001 From: James Walker Date: Fri, 12 Mar 2010 20:02:00 -0500 Subject: converting key generation to new crypt library --- plugins/OStatus/classes/Magicsig.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/classes/Magicsig.php b/plugins/OStatus/classes/Magicsig.php index d1d6a6d45..82b6017de 100644 --- a/plugins/OStatus/classes/Magicsig.php +++ b/plugins/OStatus/classes/Magicsig.php @@ -102,7 +102,17 @@ class Magicsig extends Memcached_DataObject public function generate($user_id, $key_length = 512) { - // @fixme new key generation + $rsa = new Crypt_RSA(); + + extract($rsa->createKey()); + + $rsa->loadKey($privatekey); + + $this->privateKey = $rsa; + + $this->publicKey = new Crypt_RSA(); + $this->publicKey->loadKey($publickey); + $this->user_id = $user_id; $this->insert(); } @@ -113,7 +123,7 @@ class Magicsig extends Memcached_DataObject $mod = base64_url_encode($this->publicKey->modulus->toBytes()); $exp = base64_url_encode($this->publicKey->exponent->toBytes()); $private_exp = ''; - if ($full_pair && $private_key->getExponent()) { + if ($full_pair && $this->privateKey->exponent->toBytes()) { $private_exp = '.' . base64_url_encode($this->privateKey->exponent->toBytes()); } -- cgit v1.2.3-54-g00ecf From 135c0c8a7fa56a6d4008f73314bb6f18551e869a Mon Sep 17 00:00:00 2001 From: James Walker Date: Fri, 12 Mar 2010 21:44:18 -0500 Subject: cleaning up key generation --- plugins/OStatus/classes/Magicsig.php | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/classes/Magicsig.php b/plugins/OStatus/classes/Magicsig.php index 82b6017de..73690965f 100644 --- a/plugins/OStatus/classes/Magicsig.php +++ b/plugins/OStatus/classes/Magicsig.php @@ -40,8 +40,8 @@ class Magicsig extends Memcached_DataObject public $keypair; public $alg; - private $publicKey; - private $privateKey; + public $publicKey; + public $privateKey; public function __construct($alg = 'RSA-SHA256') { @@ -100,18 +100,19 @@ class Magicsig extends Memcached_DataObject return parent::insert(); } - public function generate($user_id, $key_length = 512) + public function generate($user_id) { $rsa = new Crypt_RSA(); + + $keypair = $rsa->createKey(); - extract($rsa->createKey()); - - $rsa->loadKey($privatekey); + $rsa->loadKey($keypair['privatekey']); - $this->privateKey = $rsa; + $this->privateKey = new Crypt_RSA(); + $this->privateKey->loadKey($keypair['privatekey']); $this->publicKey = new Crypt_RSA(); - $this->publicKey->loadKey($publickey); + $this->publicKey->loadKey($keypair['publickey']); $this->user_id = $user_id; $this->insert(); @@ -186,7 +187,7 @@ class Magicsig extends Memcached_DataObject switch ($this->alg) { case 'RSA-SHA256': - return 'magicsig_sha256'; + return 'sha256'; } } -- cgit v1.2.3-54-g00ecf From 9111c5c6fe3507d69e0908d48339ddc484c64d70 Mon Sep 17 00:00:00 2001 From: James Walker Date: Sat, 13 Mar 2010 14:36:51 -0500 Subject: allow profile_url to be used in ostatus:attention --- plugins/OStatus/actions/usersalmon.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/actions/usersalmon.php b/plugins/OStatus/actions/usersalmon.php index c8a16e06f..15e8c1869 100644 --- a/plugins/OStatus/actions/usersalmon.php +++ b/plugins/OStatus/actions/usersalmon.php @@ -82,7 +82,8 @@ class UsersalmonAction extends SalmonAction throw new ClientException("In reply to a notice not by this user"); } } else if (!empty($context->attention)) { - if (!in_array($this->user->uri, $context->attention)) { + if (!in_array($this->user->uri, $context->attention) && + !in_array(common_profile_url($this->user->nickname), $context->attention)) { common_log(LOG_ERR, "{$this->user->uri} not in attention list (".implode(',', $context->attention).")"); throw new ClientException("To the attention of user(s) not including this one!"); } -- cgit v1.2.3-54-g00ecf From 56402597ddc8fbae0777ceaefc90d0473b8d31a4 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Sat, 13 Mar 2010 12:19:07 -0800 Subject: Throw a quick button label into ostatus .po file for french to test with --- plugins/OStatus/locale/fr/LC_MESSAGES/OStatus.po | 3 +++ 1 file changed, 3 insertions(+) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/locale/fr/LC_MESSAGES/OStatus.po b/plugins/OStatus/locale/fr/LC_MESSAGES/OStatus.po index f17dfa50a..0956d2f9b 100644 --- a/plugins/OStatus/locale/fr/LC_MESSAGES/OStatus.po +++ b/plugins/OStatus/locale/fr/LC_MESSAGES/OStatus.po @@ -104,3 +104,6 @@ msgstr "" #: actions/feedsubsettings.php:231 msgid "Previewing feed:" msgstr "" + +msgid "Confirm" +msgstr "Confirmer" -- cgit v1.2.3-54-g00ecf From 99ca84e68ed8cce97caa605fad04110d420c41b3 Mon Sep 17 00:00:00 2001 From: James Walker Date: Sat, 13 Mar 2010 15:46:54 -0500 Subject: changing keypair to text to hold a full 1024bit keypair --- plugins/OStatus/classes/Magicsig.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/classes/Magicsig.php b/plugins/OStatus/classes/Magicsig.php index 73690965f..5705ecc11 100644 --- a/plugins/OStatus/classes/Magicsig.php +++ b/plugins/OStatus/classes/Magicsig.php @@ -72,8 +72,8 @@ class Magicsig extends Memcached_DataObject { return array(new ColumnDef('user_id', 'integer', null, false, 'PRI'), - new ColumnDef('keypair', 'varchar', - 255, false), + new ColumnDef('keypair', 'text', + false, false), new ColumnDef('alg', 'varchar', 64, false)); } -- cgit v1.2.3-54-g00ecf From 85cf90cf0fb613bab38ce8e0142544a044fe0d1d Mon Sep 17 00:00:00 2001 From: James Walker Date: Sat, 13 Mar 2010 18:35:00 -0500 Subject: Performing & allowing host-meta discovery by http url (in addition to webfinger acct) --- plugins/OStatus/actions/userxrd.php | 15 +++++++++------ plugins/OStatus/lib/discovery.php | 11 ++++++----- 2 files changed, 15 insertions(+), 11 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/actions/userxrd.php b/plugins/OStatus/actions/userxrd.php index 414de9364..eb80a5ad4 100644 --- a/plugins/OStatus/actions/userxrd.php +++ b/plugins/OStatus/actions/userxrd.php @@ -32,12 +32,15 @@ class UserxrdAction extends XrdAction parent::prepare($args); $this->uri = $this->trimmed('uri'); - $acct = Discovery::normalize($this->uri); - - list($nick, $domain) = explode('@', substr(urldecode($acct), 5)); - $nick = common_canonical_nickname($nick); - - $this->user = User::staticGet('nickname', $nick); + $this->uri = Discovery::normalize($this->uri); + + if (Discovery::isWebfinger($this->uri)) { + list($nick, $domain) = explode('@', substr(urldecode($this->uri), 5)); + $nick = common_canonical_nickname($nick); + $this->user = User::staticGet('nickname', $nick); + } else { + $this->user = User::staticGet('uri', $this->uri); + } if (!$this->user) { $this->clientError(_('No such user.'), 404); return false; diff --git a/plugins/OStatus/lib/discovery.php b/plugins/OStatus/lib/discovery.php index f8449b309..df2fea64f 100644 --- a/plugins/OStatus/lib/discovery.php +++ b/plugins/OStatus/lib/discovery.php @@ -157,12 +157,13 @@ class Discovery_LRDD_Host_Meta implements Discovery_LRDD { public function discover($uri) { - if (!Discovery::isWebfinger($uri)) { - return false; + if (Discovery::isWebfinger($uri)) { + // We have a webfinger acct: - start with host-meta + list($name, $domain) = explode('@', $uri); + } else { + $domain = parse_url($uri, PHP_URL_HOST); } - - // We have a webfinger acct: - start with host-meta - list($name, $domain) = explode('@', $uri); + $url = 'http://'. $domain .'/.well-known/host-meta'; $xrd = Discovery::fetchXrd($url); -- cgit v1.2.3-54-g00ecf From 7aa49b5e87efa2aa383b446b264f00608f1a5eac Mon Sep 17 00:00:00 2001 From: James Walker Date: Mon, 15 Mar 2010 15:17:31 -0400 Subject: use canonical user url in xrd --- plugins/OStatus/lib/xrdaction.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/lib/xrdaction.php b/plugins/OStatus/lib/xrdaction.php index b3c1d8453..f1a56e0a8 100644 --- a/plugins/OStatus/lib/xrdaction.php +++ b/plugins/OStatus/lib/xrdaction.php @@ -46,10 +46,10 @@ class XrdAction extends Action if (empty($xrd->subject)) { $xrd->subject = Discovery::normalize($this->uri); } - $xrd->alias[] = common_profile_url($nick); + $xrd->alias[] = $this->user->uri; $xrd->links[] = array('rel' => Discovery::PROFILEPAGE, 'type' => 'text/html', - 'href' => common_profile_url($nick)); + 'href' => $this->user->uri); $xrd->links[] = array('rel' => Discovery::UPDATESFROM, 'href' => common_local_url('ApiTimelineUser', @@ -65,7 +65,7 @@ class XrdAction extends Action // XFN $xrd->links[] = array('rel' => 'http://gmpg.org/xfn/11', 'type' => 'text/html', - 'href' => common_profile_url($nick)); + 'href' => $this->user->uri); // FOAF $xrd->links[] = array('rel' => 'describedby', 'type' => 'application/rdf+xml', -- cgit v1.2.3-54-g00ecf From c9232d8f26f055a9a1124b4b3db510e80979bf18 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 15 Mar 2010 20:21:55 +0000 Subject: Ticket #2242: fix reading of inline XHTML content in Atom feeds for OStatus input. Lookup of the
needed to check for the XHTML namespace. --- lib/activity.php | 2 +- plugins/OStatus/scripts/testfeed.php | 89 ++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 plugins/OStatus/scripts/testfeed.php (limited to 'plugins/OStatus') diff --git a/lib/activity.php b/lib/activity.php index 6acf37a8a..ae65fe36f 100644 --- a/lib/activity.php +++ b/lib/activity.php @@ -463,7 +463,7 @@ class ActivityUtils $text = $contentEl->textContent; return htmlspecialchars_decode($text, ENT_QUOTES); } else if ($type == 'xhtml') { - $divEl = ActivityUtils::child($contentEl, 'div'); + $divEl = ActivityUtils::child($contentEl, 'div', 'http://www.w3.org/1999/xhtml'); if (empty($divEl)) { return null; } diff --git a/plugins/OStatus/scripts/testfeed.php b/plugins/OStatus/scripts/testfeed.php new file mode 100644 index 000000000..5e3ccd433 --- /dev/null +++ b/plugins/OStatus/scripts/testfeed.php @@ -0,0 +1,89 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..')); + +$longoptions = array('skip=', 'count='); + +$helptext = <<loadXML($xml)) { + print "Bad XML.\n"; + exit(1); +} + +if ($skip || $count) { + $entries = $feed->getElementsByTagNameNS(ActivityUtils::ATOM, 'entry'); + $remove = array(); + for ($i = 0; $i < $skip && $i < $entries->length; $i++) { + $item = $entries->item($i); + if ($item) { + $remove[] = $item; + } + } + if ($count) { + for ($i = $skip + $count; $i < $entries->length; $i++) { + $item = $entries->item($i); + if ($item) { + $remove[] = $item; + } + } + } + foreach ($remove as $item) { + $item->parentNode->removeChild($item); + } +} + +Event::handle('StartFeedSubReceive', array($sub, $feed)); + -- cgit v1.2.3-54-g00ecf From dfac4bfd095684daf935544ed3ae8b9e4eb9c08e Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 15 Mar 2010 20:26:42 +0000 Subject: Fix feed discovery: html:link@rel can contain multiple values; saw rel="updates alternate" in the wild at http://tantek.com/ which broke old discovery code. --- plugins/OStatus/lib/feeddiscovery.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/lib/feeddiscovery.php b/plugins/OStatus/lib/feeddiscovery.php index ff76b229e..7761ea583 100644 --- a/plugins/OStatus/lib/feeddiscovery.php +++ b/plugins/OStatus/lib/feeddiscovery.php @@ -211,11 +211,11 @@ class FeedDiscovery $type = $node->attributes->getNamedItem('type'); $href = $node->attributes->getNamedItem('href'); if ($rel && $type && $href) { - $rel = trim($rel->value); + $rel = array_filter(explode(" ", $rel->value)); $type = trim($type->value); $href = trim($href->value); - if (trim($rel) == 'alternate' && array_key_exists($type, $feeds) && empty($feeds[$type])) { + if (in_array('alternate', $rel) && array_key_exists($type, $feeds) && empty($feeds[$type])) { // Save the first feed found of each type... $feeds[$type] = $this->resolveURI($href, $base); } -- cgit v1.2.3-54-g00ecf From f21f78364a9cbde2ca535a3983b384707ad097ae Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 16 Mar 2010 11:25:18 -0500 Subject: Change the workflow to get better discovery Tried to re-structure the workflow of discovery to get more and richer data and hints. --- plugins/OStatus/actions/ostatussub.php | 5 +- plugins/OStatus/classes/Ostatus_profile.php | 233 ++++++++++++++++------------ plugins/OStatus/lib/discovery.php | 78 +++------- plugins/OStatus/lib/discoveryhints.php | 182 ++++++++++++++++++++++ plugins/OStatus/lib/feeddiscovery.php | 4 +- plugins/OStatus/lib/linkheader.php | 63 ++++++++ 6 files changed, 401 insertions(+), 164 deletions(-) create mode 100644 plugins/OStatus/lib/discoveryhints.php create mode 100644 plugins/OStatus/lib/linkheader.php (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/actions/ostatussub.php b/plugins/OStatus/actions/ostatussub.php index 65dee2392..07081c2c6 100644 --- a/plugins/OStatus/actions/ostatussub.php +++ b/plugins/OStatus/actions/ostatussub.php @@ -149,7 +149,7 @@ class OStatusSubAction extends Action $fullname = $entity->fullname; $homepage = $entity->homepage; $location = $entity->location; - + if (!$avatar) { $avatar = Avatar::defaultImage(AVATAR_PROFILE_SIZE); } @@ -242,7 +242,7 @@ class OStatusSubAction extends Action if (Validate::email($this->profile_uri)) { $this->oprofile = Ostatus_profile::ensureWebfinger($this->profile_uri); } else if (Validate::uri($this->profile_uri)) { - $this->oprofile = Ostatus_profile::ensureProfile($this->profile_uri); + $this->oprofile = Ostatus_profile::ensureProfileURL($this->profile_uri); } else { $this->error = _m("Sorry, we could not reach that address. Please make sure that the OStatus address is like nickname@example.com or http://example.net/nickname"); common_debug('Invalid address format.', __FILE__); @@ -339,7 +339,6 @@ class OStatusSubAction extends Action } } - /** * Handle posts to this form * diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 6ae8e4fd5..73f5d2322 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -708,18 +708,122 @@ class Ostatus_profile extends Memcached_DataObject * @return Ostatus_profile * @throws FeedSubException */ - public static function ensureProfile($profile_uri, $hints=array()) + + public static function ensureProfileURL($profile_url, $hints=array()) { - // Get the canonical feed URI and check it + $oprofile = self::getFromProfileURL($profile_url); + + if (!empty($oprofile)) { + return $oprofile; + } + + $hints['profileurl'] = $profile_url; + + // Fetch the URL + // XXX: HTTP caching + + $client = new HTTPClient(); + $client->setHeader('Accept', 'text/html,application/xhtml+xml'); + $response = $client->get($profile_url); + + if (!$response->isOk()) { + return null; + } + + // Check if we have a non-canonical URL + + $finalUrl = $response->getUrl(); + + if ($finalUrl != $profile_url) { + + $hints['profileurl'] = $finalUrl; + + $oprofile = self::getFromProfileURL($finalUrl); + + if (!empty($oprofile)) { + return $oprofile; + } + } + + // Try to get some hCard data + + $body = $response->getBody(); + + $hcardHints = DiscoveryHints::hcardHints($body, $finalUrl); + + if (!empty($hcardHints)) { + $hints = array_merge($hints, $hcardHints); + } + + // Check if they've got an LRDD header + + $lrdd = LinkHeader::getLink($response, 'lrdd', 'application/xrd+xml'); + + if (!empty($lrdd)) { + + $xrd = Discovery::fetchXrd($lrdd); + $xrdHints = DiscoveryHints::fromXRD($xrd); + + $hints = array_merge($hints, $xrdHints); + } + + // If discovery found a feedurl (probably from LRDD), use it. + + if (array_key_exists('feedurl', $hints)) { + return self::ensureFeedURL($hints['feedurl'], $hints); + } + + // Get the feed URL from HTML + $discover = new FeedDiscovery(); - if (isset($hints['feedurl'])) { - $feeduri = $hints['feedurl']; - $feeduri = $discover->discoverFromFeedURL($feeduri); - } else { - $feeduri = $discover->discoverFromURL($profile_uri); - $hints['feedurl'] = $feeduri; + + $feedurl = $discover->discoverFromHTML($finalUrl, $body); + + if (!empty($feedurl)) { + $hints['feedurl'] = $feedurl; + + return self::ensureFeedURL($feedurl, $hints); + } + } + + static function getFromProfileURL($profile_url) + { + $profile = Profile::staticGet('profileurl', $profile_url); + + if (empty($profile)) { + return null; + } + + // Is it a known Ostatus profile? + + $oprofile = Ostatus_profile::staticGet('profile_id', $profile->id); + + if (!empty($oprofile)) { + return $oprofile; } + // Is it a local user? + + $user = User::staticGet('id', $profile->id); + + if (!empty($user)) { + throw new Exception("'$profile_url' is the profile for local user '{$user->nickname}'."); + } + + // Continue discovery; it's a remote profile + // for OMB or some other protocol, may also + // support OStatus + + return null; + } + + public static function ensureFeedURL($feed_url, $hints=array()) + { + $discover = new FeedDiscovery(); + + $feeduri = $discover->discoverFromFeedURL($feed_url); + $hints['feedurl'] = $feeduri; + $huburi = $discover->getAtomLink('hub'); $hints['hub'] = $huburi; $salmonuri = $discover->getAtomLink(Salmon::NS_REPLIES); @@ -1303,7 +1407,7 @@ class Ostatus_profile extends Memcached_DataObject } } - // First, look it up + // Try looking it up $oprofile = Ostatus_profile::staticGet('uri', 'acct:'.$addr); @@ -1317,7 +1421,7 @@ class Ostatus_profile extends Memcached_DataObject $disco = new Discovery(); try { - $result = $disco->lookup($addr); + $xrd = $disco->lookup($addr); } catch (Exception $e) { // Save negative cache entry so we don't waste time looking it up again. // @fixme distinguish temporary failures? @@ -1327,38 +1431,26 @@ class Ostatus_profile extends Memcached_DataObject $hints = array('webfinger' => $addr); - foreach ($result->links as $link) { - switch ($link['rel']) { - case Discovery::PROFILEPAGE: - $hints['profileurl'] = $profileUrl = $link['href']; - break; - case Salmon::NS_REPLIES: - $hints['salmon'] = $salmonEndpoint = $link['href']; - break; - case Discovery::UPDATESFROM: - $hints['feedurl'] = $feedUrl = $link['href']; - break; - case Discovery::HCARD: - $hcardUrl = $link['href']; - break; - default: - common_log(LOG_NOTICE, "Don't know what to do with rel = '{$link['rel']}'"); - break; - } - } + $dhints = DiscoveryHints::fromXRD($xrd); + + $hints = array_merge($hints, $dhints); + + // If there's an Hcard, let's grab its info - if (isset($hcardUrl)) { - $hcardHints = self::slurpHcard($hcardUrl); - // Note: Webfinger > hcard - $hints = array_merge($hcardHints, $hints); + if (array_key_exists('hcard', $hints)) { + if (!array_key_exists('profileurl', $hints) || + $hints['hcard'] != $hints['profileurl']) { + $hcardHints = DiscoveryHints::fromHcardUrl($hints['hcard']); + $hints = array_merge($hcardHints, $hints); + } } // If we got a feed URL, try that - if (isset($feedUrl)) { + if (array_key_exists('feedurl', $hints)) { try { common_log(LOG_INFO, "Discovery on acct:$addr with feed URL $feedUrl"); - $oprofile = self::ensureProfile($feedUrl, $hints); + $oprofile = self::ensureFeedURL($hints['feedurl'], $hints); self::cacheSet(sprintf('ostatus_profile:webfinger:%s', $addr), $oprofile->uri); return $oprofile; } catch (Exception $e) { @@ -1369,10 +1461,10 @@ class Ostatus_profile extends Memcached_DataObject // If we got a profile page, try that! - if (isset($profileUrl)) { + if (array_key_exists('profileurl', $hints)) { try { common_log(LOG_INFO, "Discovery on acct:$addr with profile URL $profileUrl"); - $oprofile = self::ensureProfile($profileUrl, $hints); + $oprofile = self::ensureProfile($hints['profileurl'], $hints); self::cacheSet(sprintf('ostatus_profile:webfinger:%s', $addr), $oprofile->uri); return $oprofile; } catch (Exception $e) { @@ -1384,7 +1476,9 @@ class Ostatus_profile extends Memcached_DataObject // XXX: try hcard // XXX: try FOAF - if (isset($salmonEndpoint)) { + if (array_key_exists('salmon', $hints)) { + + $salmonEndpoint = $hints['salmon']; // An account URL, a salmon endpoint, and a dream? Not much to go // on, but let's give it a try @@ -1464,67 +1558,4 @@ class Ostatus_profile extends Memcached_DataObject return $file; } - - protected static function slurpHcard($url) - { - set_include_path(get_include_path() . PATH_SEPARATOR . INSTALLDIR . '/plugins/OStatus/extlib/hkit/'); - require_once('hkit.class.php'); - - $h = new hKit; - - // Google Buzz hcards need to be tidied. Probably others too. - - $h->tidy_mode = 'proxy'; // 'proxy', 'exec', 'php' or 'none' - - // Get by URL - $hcards = $h->getByURL('hcard', $url); - - if (empty($hcards)) { - return array(); - } - - // @fixme more intelligent guess on multi-hcard pages - $hcard = $hcards[0]; - - $hints = array(); - - $hints['profileurl'] = $url; - - if (array_key_exists('nickname', $hcard)) { - $hints['nickname'] = $hcard['nickname']; - } - - if (array_key_exists('fn', $hcard)) { - $hints['fullname'] = $hcard['fn']; - } else if (array_key_exists('n', $hcard)) { - $hints['fullname'] = implode(' ', $hcard['n']); - } - - if (array_key_exists('photo', $hcard)) { - $hints['avatar'] = $hcard['photo']; - } - - if (array_key_exists('note', $hcard)) { - $hints['bio'] = $hcard['note']; - } - - if (array_key_exists('adr', $hcard)) { - if (is_string($hcard['adr'])) { - $hints['location'] = $hcard['adr']; - } else if (is_array($hcard['adr'])) { - $hints['location'] = implode(' ', $hcard['adr']); - } - } - - if (array_key_exists('url', $hcard)) { - if (is_string($hcard['url'])) { - $hints['homepage'] = $hcard['url']; - } else if (is_array($hcard['url'])) { - // HACK get the last one; that's how our hcards look - $hints['homepage'] = $hcard['url'][count($hcard['url'])-1]; - } - } - - return $hints; - } } diff --git a/plugins/OStatus/lib/discovery.php b/plugins/OStatus/lib/discovery.php index f8449b309..6d245677a 100644 --- a/plugins/OStatus/lib/discovery.php +++ b/plugins/OStatus/lib/discovery.php @@ -40,7 +40,7 @@ class Discovery const PROFILEPAGE = 'http://webfinger.net/rel/profile-page'; const UPDATESFROM = 'http://schemas.google.com/g/2010#updates-from'; const HCARD = 'http://microformats.org/profile/hcard'; - + public $methods = array(); public function __construct() @@ -50,12 +50,11 @@ class Discovery $this->registerMethod('Discovery_LRDD_Link_HTML'); } - public function registerMethod($class) { $this->methods[] = $class; } - + /** * Given a "user id" make sure it's normalized to either a webfinger * acct: uri or a profile HTTP URL. @@ -78,7 +77,7 @@ class Discovery public static function isWebfinger($user_id) { $uri = Discovery::normalize($user_id); - + return (substr($uri, 0, 5) == 'acct:'); } @@ -99,7 +98,7 @@ class Discovery } else { $xrd_uri = $link['href']; } - + $xrd = $this->fetchXrd($xrd_uri); if ($xrd) { return $xrd; @@ -114,14 +113,13 @@ class Discovery if (!is_array($links)) { return false; } - + foreach ($links as $link) { if ($link['rel'] == $service) { return $link; } } } - public static function applyTemplate($template, $id) { @@ -130,7 +128,6 @@ class Discovery return $template; } - public static function fetchXrd($url) { try { @@ -171,7 +168,7 @@ class Discovery_LRDD_Host_Meta implements Discovery_LRDD if ($xrd->host != $domain) { return false; } - + return $xrd->links; } } @@ -187,7 +184,7 @@ class Discovery_LRDD_Link_Header implements Discovery_LRDD } catch (HTTP_Request2_Exception $e) { return false; } - + if ($response->getStatus() != 200) { return false; } @@ -196,51 +193,17 @@ class Discovery_LRDD_Link_Header implements Discovery_LRDD if (!$link_header) { // return false; } - + return Discovery_LRDD_Link_Header::parseHeader($link_header); } protected static function parseHeader($header) { - preg_match('/^<[^>]+>/', $header, $uri_reference); - //if (empty($uri_reference)) return; - - $links = array(); - - $link_uri = trim($uri_reference[0], '<>'); - $link_rel = array(); - $link_type = null; - - // remove uri-reference from header - $header = substr($header, strlen($uri_reference[0])); - - // parse link-params - $params = explode(';', $header); - - foreach ($params as $param) { - if (empty($param)) continue; - list($param_name, $param_value) = explode('=', $param, 2); - $param_name = trim($param_name); - $param_value = preg_replace('(^"|"$)', '', trim($param_value)); - - // for now we only care about 'rel' and 'type' link params - // TODO do something with the other links-params - switch ($param_name) { - case 'rel': - $link_rel = trim($param_value); - break; - - case 'type': - $link_type = trim($param_value); - } - } - - $links[] = array( - 'href' => $link_uri, - 'rel' => $link_rel, - 'type' => $link_type); + $lh = new LinkHeader($header); - return $links; + return array('href' => $lh->href, + 'rel' => $lh->rel, + 'type' => $lh->type); } } @@ -262,49 +225,48 @@ class Discovery_LRDD_Link_HTML implements Discovery_LRDD return Discovery_LRDD_Link_HTML::parse($response->getBody()); } - public function parse($html) { $links = array(); - + preg_match('/]*)?>(.*?)<\/head>/is', $html, $head_matches); $head_html = $head_matches[2]; - + preg_match_all('/]*>/i', $head_html, $link_matches); - + foreach ($link_matches[0] as $link_html) { $link_url = null; $link_rel = null; $link_type = null; - + preg_match('/\srel=(("|\')([^\\2]*?)\\2|[^"\'\s]+)/i', $link_html, $rel_matches); if ( isset($rel_matches[3]) ) { $link_rel = $rel_matches[3]; } else if ( isset($rel_matches[1]) ) { $link_rel = $rel_matches[1]; } - + preg_match('/\shref=(("|\')([^\\2]*?)\\2|[^"\'\s]+)/i', $link_html, $href_matches); if ( isset($href_matches[3]) ) { $link_uri = $href_matches[3]; } else if ( isset($href_matches[1]) ) { $link_uri = $href_matches[1]; } - + preg_match('/\stype=(("|\')([^\\2]*?)\\2|[^"\'\s]+)/i', $link_html, $type_matches); if ( isset($type_matches[3]) ) { $link_type = $type_matches[3]; } else if ( isset($type_matches[1]) ) { $link_type = $type_matches[1]; } - + $links[] = array( 'href' => $link_url, 'rel' => $link_rel, 'type' => $link_type, ); } - + return $links; } } diff --git a/plugins/OStatus/lib/discoveryhints.php b/plugins/OStatus/lib/discoveryhints.php new file mode 100644 index 000000000..db13793dd --- /dev/null +++ b/plugins/OStatus/lib/discoveryhints.php @@ -0,0 +1,182 @@ +. + */ + +class DiscoveryHints { + + static function fromXRD($xrd) + { + $hints = array(); + + foreach ($xrd->links as $link) { + switch ($link['rel']) { + case Discovery::PROFILEPAGE: + $hints['profileurl'] = $link['href']; + break; + case Salmon::NS_REPLIES: + $hints['salmon'] = $link['href']; + break; + case Discovery::UPDATESFROM: + $hints['feedurl'] = $link['href']; + break; + case Discovery::HCARD: + $hints['hcardurl'] = $link['href']; + break; + default: + break; + } + } + + return $hints; + } + + static function fromHcardUrl($url) + { + $client = new HTTPClient(); + $client->setHeader('Accept', 'text/html,application/xhtml+xml'); + $response = $client->get($url); + + if (!$response->isOk()) { + return null; + } + + return self::hcardHints($response->getBody(), + $response->getUrl()); + } + + static function hcardHints($body, $url) + { + common_debug("starting tidy"); + + $body = self::_tidy($body); + + common_debug("done with tidy"); + + set_include_path(get_include_path() . PATH_SEPARATOR . INSTALLDIR . '/plugins/OStatus/extlib/hkit/'); + require_once('hkit.class.php'); + + $h = new hKit; + + $hcards = $h->getByString('hcard', $body); + + if (empty($hcards)) { + return array(); + } + + if (count($hcards) == 1) { + $hcard = $hcards[0]; + } else { + foreach ($hcards as $try) { + if (array_key_exists('url', $try)) { + if (is_string($try['url']) && $try['url'] == $url) { + $hcard = $try; + break; + } else if (is_array($try['url'])) { + foreach ($try['url'] as $tryurl) { + if ($tryurl == $url) { + $hcard = $try; + break 2; + } + } + } + } + } + // last chance; grab the first one + if (empty($hcard)) { + $hcard = $hcards[0]; + } + } + + $hints = array(); + + if (array_key_exists('nickname', $hcard)) { + $hints['nickname'] = $hcard['nickname']; + } + + if (array_key_exists('fn', $hcard)) { + $hints['fullname'] = $hcard['fn']; + } else if (array_key_exists('n', $hcard)) { + $hints['fullname'] = implode(' ', $hcard['n']); + } + + if (array_key_exists('photo', $hcard)) { + $hints['avatar'] = $hcard['photo']; + } + + if (array_key_exists('note', $hcard)) { + $hints['bio'] = $hcard['note']; + } + + if (array_key_exists('adr', $hcard)) { + if (is_string($hcard['adr'])) { + $hints['location'] = $hcard['adr']; + } else if (is_array($hcard['adr'])) { + $hints['location'] = implode(' ', $hcard['adr']); + } + } + + if (array_key_exists('url', $hcard)) { + if (is_string($hcard['url'])) { + $hints['homepage'] = $hcard['url']; + } else if (is_array($hcard['url'])) { + // HACK get the last one; that's how our hcards look + $hints['homepage'] = $hcard['url'][count($hcard['url'])-1]; + } + } + + return $hints; + } + + private static function _tidy($body) + { + if (function_exists('tidy_parse_string')) { + common_debug("Tidying with extension"); + $text = tidy_parse_string($body); + $text = tidy_clean_repair($text); + return $body; + } else if ($fullpath = self::_findProgram('tidy')) { + common_debug("Tidying with program $fullpath"); + $tempfile = tempnam('/tmp', 'snht'); // statusnet hcard tidy + file_put_contents($tempfile, $source); + exec("$fullpath -utf8 -indent -asxhtml -numeric -bare -quiet $tempfile", $tidy); + unlink($tempfile); + return implode("\n", $tidy); + } else { + common_debug("Not tidying."); + return $body; + } + } + + private static function _findProgram($name) + { + $path = $_ENV['PATH']; + + $parts = explode(':', $path); + + foreach ($parts as $part) { + $fullpath = $part . '/' . $name; + if (is_executable($fullpath)) { + return $fullpath; + } + } + + return null; + } +} diff --git a/plugins/OStatus/lib/feeddiscovery.php b/plugins/OStatus/lib/feeddiscovery.php index ff76b229e..f9ea3e713 100644 --- a/plugins/OStatus/lib/feeddiscovery.php +++ b/plugins/OStatus/lib/feeddiscovery.php @@ -117,7 +117,7 @@ class FeedDiscovery return $this->discoverFromURL($target, false); } } - + return $this->initFromResponse($response); } @@ -202,7 +202,7 @@ class FeedDiscovery 'application/atom+xml' => false, 'application/rss+xml' => false, ); - + $nodes = $dom->getElementsByTagName('link'); for ($i = 0; $i < $nodes->length; $i++) { $node = $nodes->item($i); diff --git a/plugins/OStatus/lib/linkheader.php b/plugins/OStatus/lib/linkheader.php new file mode 100644 index 000000000..2f6c66dc9 --- /dev/null +++ b/plugins/OStatus/lib/linkheader.php @@ -0,0 +1,63 @@ +]+>/', $str, $uri_reference); + //if (empty($uri_reference)) return; + + $this->uri = trim($uri_reference[0], '<>'); + $this->rel = array(); + $this->type = null; + + // remove uri-reference from header + $str = substr($str, strlen($uri_reference[0])); + + // parse link-params + $params = explode(';', $str); + + foreach ($params as $param) { + if (empty($param)) continue; + list($param_name, $param_value) = explode('=', $param, 2); + $param_name = trim($param_name); + $param_value = preg_replace('(^"|"$)', '', trim($param_value)); + + // for now we only care about 'rel' and 'type' link params + // TODO do something with the other links-params + switch ($param_name) { + case 'rel': + $this->rel = trim($param_value); + break; + + case 'type': + $this->type = trim($param_value); + } + } + } + + static function getLink($response, $rel=null, $type=null) + { + $headers = $response->getHeader('Link'); + + // Can get an array or string, so try to simplify the path + if (!is_array($headers)) { + $headers = array($headers); + } + + foreach ($headers as $header) { + $lh = new LinkHeader($header); + + if ((is_null($rel) || $lh->rel == $rel) && + (is_null($type) || $lh->type == $type)) { + return $lh->href; + } + } + + return null; + } +} \ No newline at end of file -- cgit v1.2.3-54-g00ecf From 3a72c70b7e560023ecb7439be680942e4d0e6350 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 17 Mar 2010 12:34:35 -0700 Subject: When too-long messages come in via OStatus, mark the attachment link up as a "more" link in the HTML output, marked with class="attachment more" so JS code can fold it out smartly. Text output will still include the raw link. --- plugins/OStatus/classes/Ostatus_profile.php | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 73f5d2322..7b18fed9c 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -547,9 +547,19 @@ class Ostatus_profile extends Memcached_DataObject $shortSummary = substr($shortSummary, 0, Notice::maxContent() - (mb_strlen($url) + 2)); - $shortSummary .= '… ' . $url; - $content = $shortSummary; - $rendered = common_render_text($content); + $shortSummary .= '…'; + $content = $shortSummary . ' ' . $url; + + // We mark up the attachment link specially for the HTML output + // so we can fold-out the full version inline. + $rendered = common_render_text($shortSummary) . + ' ' . + '' . + // TRANS: expansion link for too-long remote messages + htmlspecialchars(_m('(more)')) . + ''; } } -- cgit v1.2.3-54-g00ecf From a9c731798e2f82c4961240627b41b61e29e3aedd Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 17 Mar 2010 14:16:43 -0700 Subject: Tweak to OStatus long message cropping: use original source notice URL for the link in the text version, don't shorten the link for the HTML so we can append params to it in JS. --- plugins/OStatus/classes/Ostatus_profile.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 7b18fed9c..9d2be8abc 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -542,8 +542,7 @@ class Ostatus_profile extends Memcached_DataObject } $shortSummary = common_shorten_links($summary); if (Notice::contentTooLong($shortSummary)) { - $url = common_shorten_url(common_local_url('attachment', - array('attachment' => $attachment->id))); + $url = common_shorten_url($sourceUrl); $shortSummary = substr($shortSummary, 0, Notice::maxContent() - (mb_strlen($url) + 2)); @@ -552,10 +551,12 @@ class Ostatus_profile extends Memcached_DataObject // We mark up the attachment link specially for the HTML output // so we can fold-out the full version inline. + $attachUrl = common_local_url('attachment', + array('attachment' => $attachment->id)); $rendered = common_render_text($shortSummary) . ' ' . '' . // TRANS: expansion link for too-long remote messages htmlspecialchars(_m('(more)')) . -- cgit v1.2.3-54-g00ecf From dc71833ce6fcedb4eafe22fccaa0b5302bde6e7f Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 17 Mar 2010 15:49:10 -0700 Subject: Add doc comments on saveHTMLFile; drop the extra
wrapping the contents to make it easier to extract without getting extra markup. --- plugins/OStatus/classes/Ostatus_profile.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 9d2be8abc..a59184b89 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -1537,10 +1537,18 @@ class Ostatus_profile extends Memcached_DataObject throw new Exception("Couldn't find a valid profile for '$addr'"); } + /** + * Store the full-length scrubbed HTML of a remote notice to an attachment + * file on our server. We'll link to this at the end of the cropped version. + * + * @param string $title plaintext for HTML page's title + * @param string $rendered HTML fragment for HTML page's body + * @return File + */ function saveHTMLFile($title, $rendered) { $final = sprintf("\n%s". - '
%s
', + '%s', htmlspecialchars($title), $rendered); -- cgit v1.2.3-54-g00ecf From d8a533274fa6354072a2acb66bd1574ecaec2c02 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 18 Mar 2010 00:19:32 +0100 Subject: Updated 'more' anchor for attachments to do an XHR GET --- js/util.js | 110 +++++++++++++++------------- lib/attachmentlist.php | 2 - plugins/OStatus/classes/Ostatus_profile.php | 2 +- theme/base/css/display.css | 9 ++- theme/default/css/display.css | 3 +- theme/identica/css/display.css | 3 +- 6 files changed, 74 insertions(+), 55 deletions(-) (limited to 'plugins/OStatus') diff --git a/js/util.js b/js/util.js index f82ca992c..79fd40deb 100644 --- a/js/util.js +++ b/js/util.js @@ -399,58 +399,70 @@ var SN = { // StatusNet return; } - $.fn.jOverlay.options = { - method : 'GET', - data : '', - url : '', - color : '#000', - opacity : '0.6', - zIndex : 9999, - center : false, - imgLoading : $('address .url')[0].href+'theme/base/images/illustrations/illu_progress_loading-01.gif', - bgClickToClose : true, - success : function() { - $('#jOverlayContent').append(''); - $('#jOverlayContent button').click($.closeOverlay); - }, - timeout : 0, - autoHide : true, - css : {'max-width':'542px', 'top':'5%', 'left':'32.5%'} - }; - - notice.find('a.attachment').click(function() { - var attachId = ($(this).attr('id').substring('attachment'.length + 1)); - if (attachId) { - $().jOverlay({url: $('address .url')[0].href+'attachment/' + attachId + '/ajax'}); - return false; - } - }); + var attachment_more = notice.find('.attachment.more'); + if (attachment_more.length > 0) { + attachment_more.click(function() { + $.get($(this).attr('href')+'/ajax', null, function(data) { + notice.find('.entry-title .entry-content').html($(data).find('#attachment_view .entry-content').html()); + }); - if ($('#shownotice').length == 0) { - var t; - notice.find('a.thumbnail').hover( - function() { - var anchor = $(this); - $('a.thumbnail').children('img').hide(); - anchor.closest(".entry-title").addClass('ov'); - - if (anchor.children('img').length === 0) { - t = setTimeout(function() { - $.get($('address .url')[0].href+'attachment/' + (anchor.attr('id').substring('attachment'.length + 1)) + '/thumbnail', null, function(data) { - anchor.append(data); - }); - }, 500); - } - else { - anchor.children('img').show(); - } + return false; + }); + } + else { + $.fn.jOverlay.options = { + method : 'GET', + data : '', + url : '', + color : '#000', + opacity : '0.6', + zIndex : 9999, + center : false, + imgLoading : $('address .url')[0].href+'theme/base/images/illustrations/illu_progress_loading-01.gif', + bgClickToClose : true, + success : function() { + $('#jOverlayContent').append(''); + $('#jOverlayContent button').click($.closeOverlay); }, - function() { - clearTimeout(t); - $('a.thumbnail').children('img').hide(); - $(this).closest('.entry-title').removeClass('ov'); + timeout : 0, + autoHide : true, + css : {'max-width':'542px', 'top':'5%', 'left':'32.5%'} + }; + + notice.find('a.attachment').click(function() { + var attachId = ($(this).attr('id').substring('attachment'.length + 1)); + if (attachId) { + $().jOverlay({url: $('address .url')[0].href+'attachment/' + attachId + '/ajax'}); + return false; } - ); + }); + + if ($('#shownotice').length == 0) { + var t; + notice.find('a.thumbnail').hover( + function() { + var anchor = $(this); + $('a.thumbnail').children('img').hide(); + anchor.closest(".entry-title").addClass('ov'); + + if (anchor.children('img').length === 0) { + t = setTimeout(function() { + $.get($('address .url')[0].href+'attachment/' + (anchor.attr('id').substring('attachment'.length + 1)) + '/thumbnail', null, function(data) { + anchor.append(data); + }); + }, 500); + } + else { + anchor.children('img').show(); + } + }, + function() { + clearTimeout(t); + $('a.thumbnail').children('img').hide(); + $(this).closest('.entry-title').removeClass('ov'); + } + ); + } } }, diff --git a/lib/attachmentlist.php b/lib/attachmentlist.php index 22ae8ba07..13dafd13e 100644 --- a/lib/attachmentlist.php +++ b/lib/attachmentlist.php @@ -368,9 +368,7 @@ class Attachment extends AttachmentListItem { $body = $this->scrubHtmlFile($attachment); if ($body) { - $this->out->elementStart('div', array('class' => 'inline-attachment')); $this->out->raw($body); - $this->out->elementEnd('div'); } } diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index a59184b89..7ea8ff633 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -559,7 +559,7 @@ class Ostatus_profile extends Memcached_DataObject htmlspecialchars($attachUrl) . '" class="attachment more">' . // TRANS: expansion link for too-long remote messages - htmlspecialchars(_m('(more)')) . + htmlspecialchars(_m('more')) . '
'; } } diff --git a/theme/base/css/display.css b/theme/base/css/display.css index a2e4cdf2a..9044021f5 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -1284,9 +1284,16 @@ height:16px; position:relative; padding-left:16px; } -#attachments .attachment { +#attachments .attachment, +.notice .attachment.more { padding-left:0; } +.notice .attachment.more:before { +content:'( '; +} +.notice .attachment.more:after { +content:' )'; +} .notice .attachment img { position:absolute; top:18px; diff --git a/theme/default/css/display.css b/theme/default/css/display.css index d92a53965..c08a03061 100644 --- a/theme/default/css/display.css +++ b/theme/default/css/display.css @@ -410,7 +410,8 @@ background-position: 0 -1714px; .notice .attachment { background-position:0 -394px; } -#attachments .attachment { +#attachments .attachment, +.notice .attachment.more { background:none; } .notice-options .notice_reply { diff --git a/theme/identica/css/display.css b/theme/identica/css/display.css index 59cb3c38a..62a9e25d8 100644 --- a/theme/identica/css/display.css +++ b/theme/identica/css/display.css @@ -409,7 +409,8 @@ background-position: 0 -1714px; .notice .attachment { background-position:0 -394px; } -#attachments .attachment { +#attachments .attachment, +.notice .attachment.more { background:none; } .notice-options .notice_reply { -- cgit v1.2.3-54-g00ecf From f1af583e3e37ffb5384276222d0c1cfc1e12dd90 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 18 Mar 2010 07:55:14 -0500 Subject: catch exceptions in OStatus updates, log, and continue --- plugins/OStatus/scripts/updateostatus.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/scripts/updateostatus.php b/plugins/OStatus/scripts/updateostatus.php index d553a7d62..1ef1ee829 100644 --- a/plugins/OStatus/scripts/updateostatus.php +++ b/plugins/OStatus/scripts/updateostatus.php @@ -56,7 +56,12 @@ try { $user = new User(); if ($user->find()) { while ($user->fetch()) { - updateOStatus($user); + try { + updateOStatus($user); + } catch (Exception $e) { + common_log(LOG_NOTICE, "Couldn't convert OMB subscriptions ". + "for {$user->nickname} to OStatus: " . $e->getMessage()); + } } } } else { @@ -118,8 +123,7 @@ function updateOStatus($user) if (!have_option('q', 'quiet')) { echo "fail.\n"; } - continue; - common_log(LOG_WARNING, "Couldn't convert OMB subscription (" . $up->nickname . ", " . $rp->nickname . + common_log(LOG_NOTICE, "Couldn't convert OMB subscription (" . $up->nickname . ", " . $rp->nickname . ") to OStatus: " . $e->getMessage()); continue; } -- cgit v1.2.3-54-g00ecf From a764603e733ba102c605e09d7279a59ee0d57e22 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 18 Mar 2010 08:04:22 -0500 Subject: reverse order from delete, insert to insert, delete on ostatus conversion --- plugins/OStatus/scripts/updateostatus.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/scripts/updateostatus.php b/plugins/OStatus/scripts/updateostatus.php index 1ef1ee829..315b49273 100644 --- a/plugins/OStatus/scripts/updateostatus.php +++ b/plugins/OStatus/scripts/updateostatus.php @@ -112,8 +112,8 @@ function updateOStatus($user) if (!have_option('q', 'quiet')) { echo "Converting..."; } - Subscription::cancel($up, $rp); Subscription::start($up, $op->localProfile()); + Subscription::cancel($up, $rp); if (!have_option('q', 'quiet')) { echo "done.\n"; } -- cgit v1.2.3-54-g00ecf From 052466ceab9666b30b6ee38bbb5774b1070618ad Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 18 Mar 2010 14:26:30 +0100 Subject: Using … (hellip) instead of "more" for link text --- plugins/OStatus/classes/Ostatus_profile.php | 11 ++++------- theme/base/css/display.css | 6 ------ 2 files changed, 4 insertions(+), 13 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 7ea8ff633..e47b6973d 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -546,7 +546,6 @@ class Ostatus_profile extends Memcached_DataObject $shortSummary = substr($shortSummary, 0, Notice::maxContent() - (mb_strlen($url) + 2)); - $shortSummary .= '…'; $content = $shortSummary . ' ' . $url; // We mark up the attachment link specially for the HTML output @@ -554,12 +553,10 @@ class Ostatus_profile extends Memcached_DataObject $attachUrl = common_local_url('attachment', array('attachment' => $attachment->id)); $rendered = common_render_text($shortSummary) . - ' ' . - '' . - // TRANS: expansion link for too-long remote messages - htmlspecialchars(_m('more')) . + '' . + '…' . ''; } } diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 9044021f5..757ce7ff1 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -1288,12 +1288,6 @@ padding-left:16px; .notice .attachment.more { padding-left:0; } -.notice .attachment.more:before { -content:'( '; -} -.notice .attachment.more:after { -content:' )'; -} .notice .attachment img { position:absolute; top:18px; -- cgit v1.2.3-54-g00ecf From f1b3d84b7d55cc7c466982a95815ba1ddf130418 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 18 Mar 2010 13:13:57 -0700 Subject: Fix some remote subscription regressions from f21f78364a9cbde2ca535a3983b384707ad097ae Ostatus_profile::ensureProfile() has been renamed to Ostatus_profile::ensureProfileURL() --- plugins/OStatus/OStatusPlugin.php | 4 ++-- plugins/OStatus/classes/Ostatus_profile.php | 2 +- plugins/OStatus/scripts/updateostatus.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index b472ae242..58f373e45 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -290,7 +290,7 @@ class OStatusPlugin extends Plugin $url = "$scheme://$target"; $this->log(LOG_INFO, "Checking profile address '$url'"); try { - $oprofile = Ostatus_profile::ensureProfile($url); + $oprofile = Ostatus_profile::ensureProfileURL($url); if ($oprofile && !$oprofile->isGroup()) { $profile = $oprofile->localProfile(); $matches[$pos] = array('mentioned' => array($profile), @@ -392,7 +392,7 @@ class OStatusPlugin extends Plugin foreach ($urls as $url) { try { - return Ostatus_profile::ensureProfile($url); + return Ostatus_profile::ensureProfileURL($url); } catch (Exception $e) { common_log(LOG_ERR, 'Profile lookup failed for ' . $arg . ': ' . $e->getMessage()); diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 73f5d2322..f8fda4162 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -1464,7 +1464,7 @@ class Ostatus_profile extends Memcached_DataObject if (array_key_exists('profileurl', $hints)) { try { common_log(LOG_INFO, "Discovery on acct:$addr with profile URL $profileUrl"); - $oprofile = self::ensureProfile($hints['profileurl'], $hints); + $oprofile = self::ensureProfileURL($hints['profileurl'], $hints); self::cacheSet(sprintf('ostatus_profile:webfinger:%s', $addr), $oprofile->uri); return $oprofile; } catch (Exception $e) { diff --git a/plugins/OStatus/scripts/updateostatus.php b/plugins/OStatus/scripts/updateostatus.php index 315b49273..622ded56a 100644 --- a/plugins/OStatus/scripts/updateostatus.php +++ b/plugins/OStatus/scripts/updateostatus.php @@ -103,7 +103,7 @@ function updateOStatus($user) echo "Checking {$rp->nickname}..."; } - $op = Ostatus_profile::ensureProfile($rp->profileurl); + $op = Ostatus_profile::ensureProfileURL($rp->profileurl); if (empty($op)) { echo "can't convert.\n"; -- cgit v1.2.3-54-g00ecf From cac9d23498808a60dfb890aef437606e98106a6d Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 18 Mar 2010 14:26:32 -0700 Subject: Fix for xmpp/sms notification options appearing to be disabled on new subscriptions. Base problem is that our caching-on-insert interferes with relying on column default values; the cached object is missing those fields, so they appear to be empty (null) when the object is retrieved from cache. Now explicitly setting them when inserting subscriptions, and cleaned up some code that had alternate code paths. May also have made auto-subscription work for remote OStatus subscribers, but can't test until magic sigs are working again. --- classes/Subscription.php | 56 +++++++++++++++-------------- classes/User.php | 27 +++----------- plugins/OStatus/actions/ostatussub.php | 2 +- plugins/OStatus/classes/Ostatus_profile.php | 46 ------------------------ 4 files changed, 35 insertions(+), 96 deletions(-) (limited to 'plugins/OStatus') diff --git a/classes/Subscription.php b/classes/Subscription.php index 5ac95f922..97c44a2e4 100644 --- a/classes/Subscription.php +++ b/classes/Subscription.php @@ -75,20 +75,7 @@ class Subscription extends Memcached_DataObject } if (Event::handle('StartSubscribe', array($subscriber, $other))) { - - $sub = new Subscription(); - - $sub->subscriber = $subscriber->id; - $sub->subscribed = $other->id; - $sub->created = common_sql_now(); - - $result = $sub->insert(); - - if (!$result) { - common_log_db_error($sub, 'INSERT', __FILE__); - throw new Exception(_('Could not save subscription.')); - } - + $sub = self::saveNew($subscriber->id, $other->id); $sub->notify(); self::blow('user:notices_with_friends:%d', $subscriber->id); @@ -103,20 +90,11 @@ class Subscription extends Memcached_DataObject !self::exists($other, $subscriber) && !$subscriber->hasBlocked($other)) { - $auto = new Subscription(); - - $auto->subscriber = $other->id; - $auto->subscribed = $subscriber->id; - $auto->created = common_sql_now(); - - $result = $auto->insert(); - - if (!$result) { - common_log_db_error($auto, 'INSERT', __FILE__); - throw new Exception(_('Could not save subscription.')); + try { + self::start($other, $subscriber); + } catch (Exception $e) { + common_log(LOG_ERR, "Exception during autosubscribe of {$other->nickname} to profile {$subscriber->id}: {$e->getMessage()}"); } - - $auto->notify(); } Event::handle('EndSubscribe', array($subscriber, $other)); @@ -125,6 +103,30 @@ class Subscription extends Memcached_DataObject return true; } + /** + * Low-level subscription save. + * Outside callers should use Subscription::start() + */ + protected function saveNew($subscriber_id, $other_id) + { + $sub = new Subscription(); + + $sub->subscriber = $subscriber_id; + $sub->subscribed = $other_id; + $sub->jabber = 1; + $sub->sms = 1; + $sub->created = common_sql_now(); + + $result = $sub->insert(); + + if (!$result) { + common_log_db_error($sub, 'INSERT', __FILE__); + throw new Exception(_('Could not save subscription.')); + } + + return $sub; + } + function notify() { # XXX: add other notifications (Jabber, SMS) here diff --git a/classes/User.php b/classes/User.php index 16c256535..8ad2ec63d 100644 --- a/classes/User.php +++ b/classes/User.php @@ -153,19 +153,12 @@ class User extends Memcached_DataObject return Sms_carrier::staticGet('id', $this->carrier); } + /** + * @deprecated use Subscription::start($sub, $other); + */ function subscribeTo($other) { - $sub = new Subscription(); - $sub->subscriber = $this->id; - $sub->subscribed = $other->id; - - $sub->created = common_sql_now(); // current time - - if (!$sub->insert()) { - return false; - } - - return true; + return Subscription::start($this->getProfile(), $other); } function hasBlocked($other) @@ -346,17 +339,7 @@ class User extends Memcached_DataObject common_log(LOG_WARNING, sprintf("Default user %s does not exist.", $defnick), __FILE__); } else { - $defsub = new Subscription(); - $defsub->subscriber = $user->id; - $defsub->subscribed = $defuser->id; - $defsub->created = $user->created; - - $result = $defsub->insert(); - - if (!$result) { - common_log_db_error($defsub, 'INSERT', __FILE__); - return false; - } + Subscription::start($user, $defuser); } } diff --git a/plugins/OStatus/actions/ostatussub.php b/plugins/OStatus/actions/ostatussub.php index 07081c2c6..994af6e95 100644 --- a/plugins/OStatus/actions/ostatussub.php +++ b/plugins/OStatus/actions/ostatussub.php @@ -299,7 +299,7 @@ class OStatusSubAction extends Action if ($user->isSubscribed($local)) { // TRANS: OStatus remote subscription dialog error. $this->showForm(_m('Already subscribed!')); - } elseif ($this->oprofile->subscribeLocalToRemote($user)) { + } elseif (Subscription::start($user, $local)) { $this->success(); } else { // TRANS: OStatus remote subscription dialog error. diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index f8fda4162..90a8d0ef4 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -194,52 +194,6 @@ class Ostatus_profile extends Memcached_DataObject } } - /** - * Subscribe a local user to this remote user. - * PuSH subscription will be started if necessary, and we'll - * send a Salmon notification to the remote server if available - * notifying them of the sub. - * - * @param User $user - * @return boolean success - * @throws FeedException - */ - public function subscribeLocalToRemote(User $user) - { - if ($this->isGroup()) { - throw new ServerException("Can't subscribe to a remote group"); - } - - if ($this->subscribe()) { - if ($user->subscribeTo($this->localProfile())) { - $this->notify($user->getProfile(), ActivityVerb::FOLLOW, $this); - return true; - } - } - return false; - } - - /** - * Mark this remote profile as subscribing to the given local user, - * and send appropriate notifications to the user. - * - * This will generally be in response to a subscription notification - * from a foreign site to our local Salmon response channel. - * - * @param User $user - * @return boolean success - */ - public function subscribeRemoteToLocal(User $user) - { - if ($this->isGroup()) { - throw new ServerException("Remote groups can't subscribe to local users"); - } - - Subscription::start($this->localProfile(), $user->getProfile()); - - return true; - } - /** * Send a subscription request to the hub for this feed. * The hub will later send us a confirmation POST to /main/push/callback. -- cgit v1.2.3-54-g00ecf From c8e3d08a8fb139003ae425e8b80296215f071b25 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 18 Mar 2010 15:11:25 -0700 Subject: Fix notice warning about unused var -- was renamed during refactoring. --- plugins/OStatus/classes/Ostatus_profile.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 90a8d0ef4..e77c8f7e9 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -1403,7 +1403,7 @@ class Ostatus_profile extends Memcached_DataObject if (array_key_exists('feedurl', $hints)) { try { - common_log(LOG_INFO, "Discovery on acct:$addr with feed URL $feedUrl"); + common_log(LOG_INFO, "Discovery on acct:$addr with feed URL " . $hints['feedurl']); $oprofile = self::ensureFeedURL($hints['feedurl'], $hints); self::cacheSet(sprintf('ostatus_profile:webfinger:%s', $addr), $oprofile->uri); return $oprofile; -- cgit v1.2.3-54-g00ecf From 1301877dfe89c57c182246c0d7ba0ff6335fd17b Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 18 Mar 2010 17:08:19 -0700 Subject: OStatus discover fixes: * Subscription::start was sometimes passing users instead of profiles to hooks, which broke OStatus subscription notifications; now normalizing to profiles for processing. * H-card parsing would trigger a lot of PHP warnings and notices in hKit. Now suppressing warnings and notices for the duration of the call to keep them out of output when display_errors is on. * H-card parsing would trigger a PHP fatal error if the source page was not well-formed XML and Tidy was not present on the system. Switched normalization to use the PHP DOM module which is always present, as we have no need for Tidy's extra features here. * Trying to fetch avatars from Google profiles failed and triggered a PHP warning due to the relative URL not being resolved during h-card parsing. Now passing profile page URL into hKit by sneaking a tag in while we normalize the HTML source. * Profile pages without a "Link" header could trigger PHP notices due to a bad NULL -> array(NULL) conversion in LinkHeader::getLink(). Now checking that there was a return value before converting single return value into array. --- classes/Subscription.php | 8 +++ lib/activity.php | 4 +- plugins/OStatus/lib/discoveryhints.php | 91 ++++++++++++++++++++++------------ plugins/OStatus/lib/linkheader.php | 24 ++++----- 4 files changed, 81 insertions(+), 46 deletions(-) (limited to 'plugins/OStatus') diff --git a/classes/Subscription.php b/classes/Subscription.php index 97c44a2e4..60c12cccc 100644 --- a/classes/Subscription.php +++ b/classes/Subscription.php @@ -62,6 +62,14 @@ class Subscription extends Memcached_DataObject static function start($subscriber, $other) { + // @fixme should we enforce this as profiles in callers instead? + if ($subscriber instanceof User) { + $subscriber = $subscriber->getProfile(); + } + if ($other instanceof User) { + $other = $other->getProfile(); + } + if (!$subscriber->hasRight(Right::SUBSCRIBE)) { throw new Exception(_('You have been banned from subscribing.')); } diff --git a/lib/activity.php b/lib/activity.php index d84eabf7c..c67d090f7 100644 --- a/lib/activity.php +++ b/lib/activity.php @@ -720,7 +720,7 @@ class ActivityObject } } - static function fromNotice($notice) + static function fromNotice(Notice $notice) { $object = new ActivityObject(); @@ -734,7 +734,7 @@ class ActivityObject return $object; } - static function fromProfile($profile) + static function fromProfile(Profile $profile) { $object = new ActivityObject(); diff --git a/plugins/OStatus/lib/discoveryhints.php b/plugins/OStatus/lib/discoveryhints.php index db13793dd..4da2ec0f1 100644 --- a/plugins/OStatus/lib/discoveryhints.php +++ b/plugins/OStatus/lib/discoveryhints.php @@ -65,17 +65,22 @@ class DiscoveryHints { { common_debug("starting tidy"); - $body = self::_tidy($body); + $body = self::_tidy($body, $url); common_debug("done with tidy"); set_include_path(get_include_path() . PATH_SEPARATOR . INSTALLDIR . '/plugins/OStatus/extlib/hkit/'); require_once('hkit.class.php'); - $h = new hKit; + // hKit code is not clean for notices and warnings + $old = error_reporting(); + error_reporting($old & ~E_NOTICE & ~E_WARNING); + $h = new hKit; $hcards = $h->getByString('hcard', $body); + error_reporting($old); + if (empty($hcards)) { return array(); } @@ -144,39 +149,61 @@ class DiscoveryHints { return $hints; } - private static function _tidy($body) + /** + * hKit needs well-formed XML for its parsing. + * We'll take the HTML body here and normalize it to XML. + * + * @param string $body HTML document source, possibly not-well-formed + * @param string $url source URL + * @return string well-formed XML document source + * @throws Exception if HTML parsing failed. + */ + private static function _tidy($body, $url) { - if (function_exists('tidy_parse_string')) { - common_debug("Tidying with extension"); - $text = tidy_parse_string($body); - $text = tidy_clean_repair($text); - return $body; - } else if ($fullpath = self::_findProgram('tidy')) { - common_debug("Tidying with program $fullpath"); - $tempfile = tempnam('/tmp', 'snht'); // statusnet hcard tidy - file_put_contents($tempfile, $source); - exec("$fullpath -utf8 -indent -asxhtml -numeric -bare -quiet $tempfile", $tidy); - unlink($tempfile); - return implode("\n", $tidy); - } else { - common_debug("Not tidying."); - return $body; + if (empty($body)) { + throw new Exception("Empty HTML could not be parsed."); } - } - - private static function _findProgram($name) - { - $path = $_ENV['PATH']; - - $parts = explode(':', $path); - - foreach ($parts as $part) { - $fullpath = $part . '/' . $name; - if (is_executable($fullpath)) { - return $fullpath; + $dom = new DOMDocument(); + + // Some HTML errors will trigger warnings, but still work. + $old = error_reporting(); + error_reporting($old & ~E_WARNING); + + $ok = $dom->loadHTML($body); + + error_reporting($old); + + if ($ok) { + // hKit doesn't give us a chance to pass the source URL for + // resolving relative links, such as the avatar photo on a + // Google profile. We'll slip it into a tag if there's + // not already one present. + $bases = $dom->getElementsByTagName('base'); + if ($bases && $bases->length >= 1) { + $base = $bases->item(0); + if ($base->hasAttribute('href')) { + $base->setAttribute('href', $url); + } + } else { + $base = $dom->createElement('base'); + $base->setAttribute('href', $url); + $heads = $dom->getElementsByTagName('head'); + if ($heads || $heads->length) { + $head = $heads->item(0); + } else { + $head = $dom->createElement('head'); + $root = $dom->documentRoot; + if ($root->firstChild) { + $root->insertBefore($head, $root->firstChild); + } else { + $root->appendChild($head); + } + } + $head->appendChild($base); } + return $dom->saveXML(); + } else { + throw new Exception("Invalid HTML could not be parsed."); } - - return null; } } diff --git a/plugins/OStatus/lib/linkheader.php b/plugins/OStatus/lib/linkheader.php index 2f6c66dc9..afcd66d26 100644 --- a/plugins/OStatus/lib/linkheader.php +++ b/plugins/OStatus/lib/linkheader.php @@ -43,21 +43,21 @@ class LinkHeader static function getLink($response, $rel=null, $type=null) { $headers = $response->getHeader('Link'); + if ($headers) { + // Can get an array or string, so try to simplify the path + if (!is_array($headers)) { + $headers = array($headers); + } - // Can get an array or string, so try to simplify the path - if (!is_array($headers)) { - $headers = array($headers); - } - - foreach ($headers as $header) { - $lh = new LinkHeader($header); + foreach ($headers as $header) { + $lh = new LinkHeader($header); - if ((is_null($rel) || $lh->rel == $rel) && - (is_null($type) || $lh->type == $type)) { - return $lh->href; + if ((is_null($rel) || $lh->rel == $rel) && + (is_null($type) || $lh->type == $type)) { + return $lh->href; + } } } - return null; } -} \ No newline at end of file +} -- cgit v1.2.3-54-g00ecf From 08faff22784c8a004a858afa7372061c2bd3d8f3 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 18 Mar 2010 17:55:21 -0700 Subject: Work around weird bug with HTML normalization via PHP DOM module; if source had xmlns and xml:lang I ended up with double output, breaking the subsequent parsing. Will have to track this down later and report upstream if not already resolved. --- plugins/OStatus/extlib/hkit/hkit.class.php | 2 +- plugins/OStatus/lib/discoveryhints.php | 21 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/extlib/hkit/hkit.class.php b/plugins/OStatus/extlib/hkit/hkit.class.php index c3a54cff6..fec6f4d8f 100644 --- a/plugins/OStatus/extlib/hkit/hkit.class.php +++ b/plugins/OStatus/extlib/hkit/hkit.class.php @@ -472,4 +472,4 @@ } -?> \ No newline at end of file +?> diff --git a/plugins/OStatus/lib/discoveryhints.php b/plugins/OStatus/lib/discoveryhints.php index 4da2ec0f1..0273b5a92 100644 --- a/plugins/OStatus/lib/discoveryhints.php +++ b/plugins/OStatus/lib/discoveryhints.php @@ -174,6 +174,26 @@ class DiscoveryHints { error_reporting($old); if ($ok) { + // If the original had xmlns or xml:lang attributes on the + // , we seen to end up with duplicates, which causes + // parse errors. Remove em! + // + // For some reason we have to iterate and remove them twice, + // *plus* they don't show up on hasAttribute() or removeAttribute(). + // This might be some weird bug in PHP or libxml2, uncertain if + // it affects other folks consistently. + $root = $dom->documentElement; + foreach ($root->attributes as $i => $x) { + if ($i == 'xmlns' || $i == 'xml:lang') { + $root->removeAttributeNode($x); + } + } + foreach ($root->attributes as $i => $x) { + if ($i == 'xmlns' || $i == 'xml:lang') { + $root->removeAttributeNode($x); + } + } + // hKit doesn't give us a chance to pass the source URL for // resolving relative links, such as the avatar photo on a // Google profile. We'll slip it into a tag if there's @@ -192,7 +212,6 @@ class DiscoveryHints { $head = $heads->item(0); } else { $head = $dom->createElement('head'); - $root = $dom->documentRoot; if ($root->firstChild) { $root->insertBefore($head, $root->firstChild); } else { -- cgit v1.2.3-54-g00ecf From 4a6c9e445149e42a4f81d5140296e7770c60bc6c Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 18 Mar 2010 17:55:21 -0700 Subject: Work around weird bug with HTML normalization via PHP DOM module; if source had xmlns and xml:lang I ended up with double output, breaking the subsequent parsing. Will have to track this down later and report upstream if not already resolved. --- plugins/OStatus/extlib/hkit/hkit.class.php | 2 +- plugins/OStatus/lib/discoveryhints.php | 21 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/extlib/hkit/hkit.class.php b/plugins/OStatus/extlib/hkit/hkit.class.php index c3a54cff6..fec6f4d8f 100644 --- a/plugins/OStatus/extlib/hkit/hkit.class.php +++ b/plugins/OStatus/extlib/hkit/hkit.class.php @@ -472,4 +472,4 @@ } -?> \ No newline at end of file +?> diff --git a/plugins/OStatus/lib/discoveryhints.php b/plugins/OStatus/lib/discoveryhints.php index 4da2ec0f1..0273b5a92 100644 --- a/plugins/OStatus/lib/discoveryhints.php +++ b/plugins/OStatus/lib/discoveryhints.php @@ -174,6 +174,26 @@ class DiscoveryHints { error_reporting($old); if ($ok) { + // If the original had xmlns or xml:lang attributes on the + // , we seen to end up with duplicates, which causes + // parse errors. Remove em! + // + // For some reason we have to iterate and remove them twice, + // *plus* they don't show up on hasAttribute() or removeAttribute(). + // This might be some weird bug in PHP or libxml2, uncertain if + // it affects other folks consistently. + $root = $dom->documentElement; + foreach ($root->attributes as $i => $x) { + if ($i == 'xmlns' || $i == 'xml:lang') { + $root->removeAttributeNode($x); + } + } + foreach ($root->attributes as $i => $x) { + if ($i == 'xmlns' || $i == 'xml:lang') { + $root->removeAttributeNode($x); + } + } + // hKit doesn't give us a chance to pass the source URL for // resolving relative links, such as the avatar photo on a // Google profile. We'll slip it into a tag if there's @@ -192,7 +212,6 @@ class DiscoveryHints { $head = $heads->item(0); } else { $head = $dom->createElement('head'); - $root = $dom->documentRoot; if ($root->firstChild) { $root->insertBefore($head, $root->firstChild); } else { -- cgit v1.2.3-54-g00ecf From 17c50f338ceb574780476f6b788f48e2d7d06017 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 18 Mar 2010 20:52:00 -0500 Subject: Remove hkit and do our own hcard parsing Parsing hcards for the data we need wasn't hard enough to justify using hkit. It was dependent on a number of external systems (something to run tidy), and only could handle XHTML. We now parse HTML with the PHP dom libraries used elsewhere, and scrape out our own hcards. Seems to work nicer and faster and most of all works with Google Buzz profile URLs. --- plugins/OStatus/extlib/hkit/hcard.profile.php | 105 ------ plugins/OStatus/extlib/hkit/hkit.class.php | 475 -------------------------- plugins/OStatus/lib/discoveryhints.php | 194 +++++++---- 3 files changed, 130 insertions(+), 644 deletions(-) delete mode 100644 plugins/OStatus/extlib/hkit/hcard.profile.php delete mode 100644 plugins/OStatus/extlib/hkit/hkit.class.php (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/extlib/hkit/hcard.profile.php b/plugins/OStatus/extlib/hkit/hcard.profile.php deleted file mode 100644 index 6ec0dc890..000000000 --- a/plugins/OStatus/extlib/hkit/hcard.profile.php +++ /dev/null @@ -1,105 +0,0 @@ -root_class = 'vcard'; - - $this->classes = array( - 'fn', array('honorific-prefix', 'given-name', 'additional-name', 'family-name', 'honorific-suffix'), - 'n', array('honorific-prefix', 'given-name', 'additional-name', 'family-name', 'honorific-suffix'), - 'adr', array('post-office-box', 'extended-address', 'street-address', 'postal-code', 'country-name', 'type', 'region', 'locality'), - 'label', 'bday', 'agent', 'nickname', 'photo', 'class', - 'email', array('type', 'value'), - 'category', 'key', 'logo', 'mailer', 'note', - 'org', array('organization-name', 'organization-unit'), - 'tel', array('type', 'value'), - 'geo', array('latitude', 'longitude'), - 'tz', 'uid', 'url', 'rev', 'role', 'sort-string', 'sound', 'title' - ); - - // classes that must only appear once per card - $this->singles = array( - 'fn' - ); - - // classes that are required (not strictly enforced - give at least one!) - $this->required = array( - 'fn' - ); - - $this->att_map = array( - 'fn' => array('IMG|alt'), - 'url' => array('A|href', 'IMG|src', 'AREA|href'), - 'photo' => array('IMG|src'), - 'bday' => array('ABBR|title'), - 'logo' => array('IMG|src'), - 'email' => array('A|href'), - 'geo' => array('ABBR|title') - ); - - - $this->callbacks = array( - 'url' => array($this, 'resolvePath'), - 'photo' => array($this, 'resolvePath'), - 'logo' => array($this, 'resolvePath'), - 'email' => array($this, 'resolveEmail') - ); - - - - function hKit_hcard_post($a) - { - - foreach ($a as &$vcard){ - - hKit_implied_n_optimization($vcard); - hKit_implied_n_from_fn($vcard); - - } - - return $a; - - } - - - function hKit_implied_n_optimization(&$vcard) - { - if (array_key_exists('fn', $vcard) && !is_array($vcard['fn']) && - !array_key_exists('n', $vcard) && (!array_key_exists('org', $vcard) || $vcard['fn'] != $vcard['org'])){ - - if (sizeof(explode(' ', $vcard['fn'])) == 2){ - $patterns = array(); - $patterns[] = array('/^(\S+),\s*(\S{1})$/', 2, 1); // Lastname, Initial - $patterns[] = array('/^(\S+)\s*(\S{1})\.*$/', 2, 1); // Lastname Initial(.) - $patterns[] = array('/^(\S+),\s*(\S+)$/', 2, 1); // Lastname, Firstname - $patterns[] = array('/^(\S+)\s*(\S+)$/', 1, 2); // Firstname Lastname - - foreach ($patterns as $pattern){ - if (preg_match($pattern[0], $vcard['fn'], $matches) === 1){ - $n = array(); - $n['given-name'] = $matches[$pattern[1]]; - $n['family-name'] = $matches[$pattern[2]]; - $vcard['n'] = $n; - - - break; - } - } - } - } - } - - - function hKit_implied_n_from_fn(&$vcard) - { - if (array_key_exists('fn', $vcard) && is_array($vcard['fn']) - && !array_key_exists('n', $vcard) && (!array_key_exists('org', $vcard) || $vcard['fn'] != $vcard['org'])){ - - $vcard['n'] = $vcard['fn']; - } - - if (array_key_exists('fn', $vcard) && is_array($vcard['fn'])){ - $vcard['fn'] = $vcard['fn']['text']; - } - } - -?> \ No newline at end of file diff --git a/plugins/OStatus/extlib/hkit/hkit.class.php b/plugins/OStatus/extlib/hkit/hkit.class.php deleted file mode 100644 index c3a54cff6..000000000 --- a/plugins/OStatus/extlib/hkit/hkit.class.php +++ /dev/null @@ -1,475 +0,0 @@ -' . implode(', ', $missing) . ''); - - } - - - public function getByURL($profile='', $url='') - { - - if ($profile=='' || $url == '') return false; - - $this->loadProfile($profile); - - $source = $this->loadURL($url); - - if ($source){ - $tidy_xhtml = $this->tidyThis($source); - - $fragment = false; - - if (strrchr($url, '#')) - $fragment = array_pop(explode('#', $url)); - - $doc = $this->loadDoc($tidy_xhtml, $fragment); - $s = $this->processNodes($doc, $this->classes); - $s = $this->postProcess($profile, $s); - - return $s; - }else{ - return false; - } - } - - public function getByString($profile='', $input_xml='') - { - if ($profile=='' || $input_xml == '') return false; - - $this->loadProfile($profile); - - $doc = $this->loadDoc($input_xml); - $s = $this->processNodes($doc, $this->classes); - $s = $this->postProcess($profile, $s); - - return $s; - - } - - private function processNodes($items, $classes, $allow_includes=true){ - - $out = array(); - - foreach($items as $item){ - $data = array(); - - for ($i=0; $ixpath($xpath); - - if ($results){ - foreach ($results as $result){ - if (isset($classes[$i+1]) && is_array($classes[$i+1])){ - $nodes = $this->processNodes($results, $classes[$i+1]); - if (sizeof($nodes) > 0){ - $nodes = array_merge(array('text'=>$this->getNodeValue($result, $classes[$i])), $nodes); - $data[$classes[$i]] = $nodes; - }else{ - $data[$classes[$i]] = $this->getNodeValue($result, $classes[$i]); - } - - }else{ - if (isset($data[$classes[$i]])){ - if (is_array($data[$classes[$i]])){ - // is already an array - append - $data[$classes[$i]][] = $this->getNodeValue($result, $classes[$i]); - - }else{ - // make it an array - if ($classes[$i] == 'value'){ // unless it's the 'value' of a type/value pattern - $data[$classes[$i]] .= $this->getNodeValue($result, $classes[$i]); - }else{ - $old_val = $data[$classes[$i]]; - $data[$classes[$i]] = array($old_val, $this->getNodeValue($result, $classes[$i])); - $old_val = false; - } - } - }else{ - // set as normal value - $data[$classes[$i]] = $this->getNodeValue($result, $classes[$i]); - - } - } - - // td@headers pattern - if (strtoupper(dom_import_simplexml($result)->tagName)== "TD" && $result['headers']){ - $include_ids = explode(' ', $result['headers']); - $doc = $this->doc; - foreach ($include_ids as $id){ - $xpath = "//*[@id='$id']/.."; - $includes = $doc->xpath($xpath); - foreach ($includes as $include){ - $tmp = $this->processNodes($include, $this->classes); - if (is_array($tmp)) $data = array_merge($data, $tmp); - } - } - } - } - } - } - $result = false; - } - - // include-pattern - if ($allow_includes){ - $xpath = ".//*[contains(concat(' ',normalize-space(@class),' '),' include ')]"; - $results = $item->xpath($xpath); - - if ($results){ - foreach ($results as $result){ - $tagName = strtoupper(dom_import_simplexml($result)->tagName); - if ((($tagName == "OBJECT" && $result['data']) || ($tagName == "A" && $result['href'])) - && preg_match('/\binclude\b/', $result['class'])){ - $att = ($tagName == "OBJECT" ? 'data' : 'href'); - $id = str_replace('#', '', $result[$att]); - $doc = $this->doc; - $xpath = "//*[@id='$id']"; - $includes = $doc->xpath($xpath); - foreach ($includes as $include){ - $include = simplexml_load_string(''.$include->asXML().''); // don't ask. - $tmp = $this->processNodes($include, $this->classes, false); - if (is_array($tmp)) $data = array_merge($data, $tmp); - } - } - } - } - } - $out[] = $data; - } - - if (sizeof($out) > 1){ - return $out; - }else if (isset($data)){ - return $data; - }else{ - return array(); - } - } - - - private function getNodeValue($node, $className) - { - - $tag_name = strtoupper(dom_import_simplexml($node)->tagName); - $s = false; - - // ignore DEL tags - if ($tag_name == 'DEL') return $s; - - // look up att map values - if (array_key_exists($className, $this->att_map)){ - - foreach ($this->att_map[$className] as $map){ - if (preg_match("/$tag_name\|/", $map)){ - $s = ''.$node[array_pop($foo = explode('|', $map))]; - } - } - } - - // if nothing and OBJ, try data. - if (!$s && $tag_name=='OBJECT' && $node['data']) $s = ''.$node['data']; - - // if nothing and IMG, try alt. - if (!$s && $tag_name=='IMG' && $node['alt']) $s = ''.$node['alt']; - - // if nothing and AREA, try alt. - if (!$s && $tag_name=='AREA' && $node['alt']) $s = ''.$node['alt']; - - //if nothing and not A, try title. - if (!$s && $tag_name!='A' && $node['title']) $s = ''.$node['title']; - - - // if nothing found, go with node text - $s = ($s ? $s : implode(array_filter($node->xpath('child::node()'), array(&$this, "filterBlankValues")), ' ')); - - // callbacks - if (array_key_exists($className, $this->callbacks)){ - $s = preg_replace_callback('/.*/', $this->callbacks[$className], $s, 1); - } - - // trim and remove line breaks - if ($tag_name != 'PRE'){ - $s = trim(preg_replace('/[\r\n\t]+/', '', $s)); - $s = trim(preg_replace('/(\s{2})+/', ' ', $s)); - } - - return $s; - } - - private function filterBlankValues($s){ - return preg_match("/\w+/", $s); - } - - - private function tidyThis($source) - { - switch ( $this->tidy_mode ) - { - case 'exec': - $tmp_file = $this->tmp_dir.md5($source).'.txt'; - file_put_contents($tmp_file, $source); - exec("tidy -utf8 -indent -asxhtml -numeric -bare -quiet $tmp_file", $tidy); - unlink($tmp_file); - return implode("\n", $tidy); - break; - - case 'php': - $tidy = tidy_parse_string($source); - return tidy_clean_repair($tidy); - break; - - default: - return $source; - break; - } - - } - - - private function loadProfile($profile) - { - require_once("$profile.profile.php"); - } - - - private function loadDoc($input_xml, $fragment=false) - { - $xml = simplexml_load_string($input_xml); - - $this->doc = $xml; - - if ($fragment){ - $doc = $xml->xpath("//*[@id='$fragment']"); - $xml = simplexml_load_string($doc[0]->asXML()); - $doc = null; - } - - // base tag - if ($xml->head->base['href']) $this->base = $xml->head->base['href']; - - // xml:base attribute - PITA with SimpleXML - preg_match('/xml:base="(.*)"/', $xml->asXML(), $matches); - if (is_array($matches) && sizeof($matches)>1) $this->base = $matches[1]; - - return $xml->xpath("//*[contains(concat(' ',normalize-space(@class),' '),' $this->root_class ')]"); - - } - - - private function loadURL($url) - { - $this->url = $url; - - if ($this->tidy_mode == 'proxy' && $this->tidy_proxy != ''){ - $url = $this->tidy_proxy . $url; - } - - return @file_get_contents($url); - - } - - - private function postProcess($profile, $s) - { - $required = $this->required; - - if (is_array($s) && array_key_exists($required[0], $s)){ - $s = array($s); - } - - $s = $this->dedupeSingles($s); - - if (function_exists('hKit_'.$profile.'_post')){ - $s = call_user_func('hKit_'.$profile.'_post', $s); - } - - $s = $this->removeTextVals($s); - - return $s; - } - - - private function resolvePath($filepath) - { // ugly code ahoy: needs a serious tidy up - - $filepath = $filepath[0]; - - $base = $this->base; - $url = $this->url; - - if ($base != '' && strpos($base, '://') !== false) - $url = $base; - - $r = parse_url($url); - $domain = $r['scheme'] . '://' . $r['host']; - - if (!isset($r['path'])) $r['path'] = '/'; - $path = explode('/', $r['path']); - $file = explode('/', $filepath); - $new = array(''); - - if (strpos($filepath, '://') !== false || strpos($filepath, 'data:') !== false){ - return $filepath; - } - - if ($file[0] == ''){ - // absolute path - return ''.$domain . implode('/', $file); - }else{ - // relative path - if ($path[sizeof($path)-1] == '') array_pop($path); - if (strpos($path[sizeof($path)-1], '.') !== false) array_pop($path); - - foreach ($file as $segment){ - if ($segment == '..'){ - array_pop($path); - }else{ - $new[] = $segment; - } - } - return ''.$domain . implode('/', $path) . implode('/', $new); - } - } - - private function resolveEmail($v) - { - $parts = parse_url($v[0]); - return ($parts['path']); - } - - - private function dedupeSingles($s) - { - $singles = $this->singles; - - foreach ($s as &$item){ - foreach ($singles as $classname){ - if (array_key_exists($classname, $item) && is_array($item[$classname])){ - if (isset($item[$classname][0])) $item[$classname] = $item[$classname][0]; - } - } - } - - return $s; - } - - private function removeTextVals($s) - { - foreach ($s as $key => &$val){ - if ($key){ - $k = $key; - }else{ - $k = ''; - } - - if (is_array($val)){ - $val = $this->removeTextVals($val); - }else{ - if ($k == 'text'){ - $val = ''; - } - } - } - - return array_filter($s); - } - - } - - -?> \ No newline at end of file diff --git a/plugins/OStatus/lib/discoveryhints.php b/plugins/OStatus/lib/discoveryhints.php index db13793dd..1bb0ad2ae 100644 --- a/plugins/OStatus/lib/discoveryhints.php +++ b/plugins/OStatus/lib/discoveryhints.php @@ -63,49 +63,12 @@ class DiscoveryHints { static function hcardHints($body, $url) { - common_debug("starting tidy"); - - $body = self::_tidy($body); - - common_debug("done with tidy"); - - set_include_path(get_include_path() . PATH_SEPARATOR . INSTALLDIR . '/plugins/OStatus/extlib/hkit/'); - require_once('hkit.class.php'); - - $h = new hKit; - - $hcards = $h->getByString('hcard', $body); - - if (empty($hcards)) { - return array(); - } - - if (count($hcards) == 1) { - $hcard = $hcards[0]; - } else { - foreach ($hcards as $try) { - if (array_key_exists('url', $try)) { - if (is_string($try['url']) && $try['url'] == $url) { - $hcard = $try; - break; - } else if (is_array($try['url'])) { - foreach ($try['url'] as $tryurl) { - if ($tryurl == $url) { - $hcard = $try; - break 2; - } - } - } - } - } - // last chance; grab the first one - if (empty($hcard)) { - $hcard = $hcards[0]; - } - } + $hcard = self::_hcard($body, $url); $hints = array(); + // XXX: don't copy stuff into an array and then copy it again + if (array_key_exists('nickname', $hcard)) { $hints['nickname'] = $hcard['nickname']; } @@ -117,7 +80,7 @@ class DiscoveryHints { } if (array_key_exists('photo', $hcard)) { - $hints['avatar'] = $hcard['photo']; + $hints['avatar'] = $hcard['photo'][0]; } if (array_key_exists('note', $hcard)) { @@ -144,39 +107,142 @@ class DiscoveryHints { return $hints; } - private static function _tidy($body) + static function _hcard($body, $url) { - if (function_exists('tidy_parse_string')) { - common_debug("Tidying with extension"); - $text = tidy_parse_string($body); - $text = tidy_clean_repair($text); - return $body; - } else if ($fullpath = self::_findProgram('tidy')) { - common_debug("Tidying with program $fullpath"); - $tempfile = tempnam('/tmp', 'snht'); // statusnet hcard tidy - file_put_contents($tempfile, $source); - exec("$fullpath -utf8 -indent -asxhtml -numeric -bare -quiet $tempfile", $tidy); - unlink($tempfile); - return implode("\n", $tidy); + // DOMDocument::loadHTML may throw warnings on unrecognized elements. + + $old = error_reporting(error_reporting() & ~E_WARNING); + + $doc = new DOMDocument(); + $doc->loadHTML($body); + + error_reporting($old); + + $xp = new DOMXPath($doc); + + $hcardNodes = self::_getChildrenByClass($doc->documentElement, 'vcard', $xp); + + $hcards = array(); + + for ($i = 0; $i < $hcardNodes->length; $i++) { + + $hcardNode = $hcardNodes->item($i); + + $hcard = self::_hcardFromNode($hcardNode, $xp, $url); + + $hcards[] = $hcard; + } + + $repr = null; + + foreach ($hcards as $hcard) { + if (in_array($url, $hcard['url'])) { + $repr = $hcard; + break; + } + } + + if (!is_null($repr)) { + return $repr; + } else if (count($hcards) > 0) { + return $hcards[0]; } else { - common_debug("Not tidying."); - return $body; + return null; } } - private static function _findProgram($name) + function _getChildrenByClass($el, $cls, $xp) + { + // borrowed from hkit. Thanks dudes! + + $qry = ".//*[contains(concat(' ',normalize-space(@class),' '),' $cls ')]"; + + $nodes = $xp->query($qry, $el); + + return $nodes; + } + + function _hcardFromNode($hcardNode, $xp, $base) { - $path = $_ENV['PATH']; + $hcard = array(); + + $hcard['url'] = array(); + + $urlNodes = self::_getChildrenByClass($hcardNode, 'url', $xp); + + for ($j = 0; $j < $urlNodes->length; $j++) { + + $urlNode = $urlNodes->item($j); + + if ($urlNode->hasAttribute('href')) { + $url = $urlNode->getAttribute('href'); + } else { + $url = $urlNode->textContent; + } + + $hcard['url'][] = self::_rel2abs($url, $base); + } + + $hcard['photo'] = array(); + + $photoNodes = self::_getChildrenByClass($hcardNode, 'photo', $xp); + + for ($j = 0; $j < $photoNodes->length; $j++) { + $photoNode = $photoNodes->item($j); + if ($photoNode->hasAttribute('src')) { + $url = $photoNode->getAttribute('src'); + } else if ($photoNode->hasAttribute('href')) { + $url = $photoNode->getAttribute('href'); + } else { + $url = $photoNode->textContent; + } + $hcard['photo'][] = self::_rel2abs($url, $base); + } + + $singles = array('nickname', 'note', 'fn', 'n', 'adr'); - $parts = explode(':', $path); + foreach ($singles as $single) { - foreach ($parts as $part) { - $fullpath = $part . '/' . $name; - if (is_executable($fullpath)) { - return $fullpath; + $nodes = self::_getChildrenByClass($hcardNode, $single, $xp); + + if ($nodes->length > 0) { + $node = $nodes->item(0); + $hcard[$single] = $node->textContent; } } - return null; + return $hcard; + } + + // XXX: this is a first pass; we probably need + // to handle things like ../ and ./ and so on + + static function _rel2abs($rel, $wrt) + { + $parts = parse_url($rel); + + if ($parts === false) { + return false; + } + + // If it's got a scheme, use it + + if ($parts['scheme'] != '') { + return $rel; + } + + $w = parse_url($wrt); + + $base = $w['scheme'].'://'.$w['host']; + + if ($rel[0] == '/') { + return $base.$rel; + } + + $wp = explode('/', $w['path']); + + array_pop($wp); + + return $base.implode('/', $wp).'/'.$rel; } } -- cgit v1.2.3-54-g00ecf From 9e3e1d3d5632b2f4690018677e2ba82beb399fca Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 19 Mar 2010 10:15:00 -0700 Subject: Validate OStatus avatar URL before fetching. --- plugins/OStatus/classes/Ostatus_profile.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index e77c8f7e9..e0e0223b8 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -839,8 +839,8 @@ class Ostatus_profile extends Memcached_DataObject } /** - * * Download and update given avatar image + * * @param string $url * @throws Exception in various failure cases */ @@ -850,6 +850,9 @@ class Ostatus_profile extends Memcached_DataObject // We've already got this one. return; } + if (!common_valid_http_url($url)) { + throw new ServerException(_m("Invalid avatar URL %s"), $url); + } if ($this->isGroup()) { $self = $this->localGroup(); -- cgit v1.2.3-54-g00ecf From d2e66641322a3297be6a3a6680d10ed7b6720c0e Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 19 Mar 2010 10:15:00 -0700 Subject: Validate OStatus avatar URL before fetching. --- plugins/OStatus/classes/Ostatus_profile.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 6ae8e4fd5..6145080fc 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -781,8 +781,8 @@ class Ostatus_profile extends Memcached_DataObject } /** - * * Download and update given avatar image + * * @param string $url * @throws Exception in various failure cases */ @@ -792,6 +792,9 @@ class Ostatus_profile extends Memcached_DataObject // We've already got this one. return; } + if (!common_valid_http_url($url)) { + throw new ServerException(_m("Invalid avatar URL %s"), $url); + } if ($this->isGroup()) { $self = $this->localGroup(); -- cgit v1.2.3-54-g00ecf From b97400bd6f49dfac71124a3243d1c27f49822f58 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 19 Mar 2010 11:17:56 -0700 Subject: clarify output on fixup-shadow.php --- plugins/OStatus/scripts/fixup-shadow.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/scripts/fixup-shadow.php b/plugins/OStatus/scripts/fixup-shadow.php index 0171b77bc..ec014c787 100644 --- a/plugins/OStatus/scripts/fixup-shadow.php +++ b/plugins/OStatus/scripts/fixup-shadow.php @@ -50,7 +50,7 @@ $encGroup = str_replace($marker, '%', $encGroup); $sql = "SELECT * FROM ostatus_profile WHERE uri LIKE '%s' OR uri LIKE '%s'"; $oprofile->query(sprintf($sql, $encProfile, $encGroup)); -echo "Found $oprofile->N bogus ostatus_profile entries:\n"; +echo "Found $oprofile->N bogus ostatus_profile entries for local users and groups:\n"; while ($oprofile->fetch()) { echo "$oprofile->uri"; @@ -58,7 +58,7 @@ while ($oprofile->fetch()) { if ($dry) { echo " (unchanged)\n"; } else { - echo " deleting..."; + echo " removing bogus ostatus_profile entry..."; $evil = clone($oprofile); $evil->delete(); echo " ok\n"; -- cgit v1.2.3-54-g00ecf From 1e03968d911fe4bc757c01036365a5f544621088 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 19 Mar 2010 15:23:30 -0500 Subject: define a 'root' attribute for the channel or feed --- plugins/OStatus/lib/feeddiscovery.php | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/lib/feeddiscovery.php b/plugins/OStatus/lib/feeddiscovery.php index 7de80b335..4809f9d35 100644 --- a/plugins/OStatus/lib/feeddiscovery.php +++ b/plugins/OStatus/lib/feeddiscovery.php @@ -73,6 +73,7 @@ class FeedDiscovery public $uri; public $type; public $feed; + public $root; /** Post-initialize query helper... */ public function getLink($rel, $type=null) @@ -83,7 +84,7 @@ class FeedDiscovery public function getAtomLink($rel, $type=null) { - return ActivityUtils::getLink($this->feed->documentElement, $rel, $type); + return ActivityUtils::getLink($this->root, $rel, $type); } /** @@ -154,9 +155,27 @@ class FeedDiscovery $this->uri = $sourceurl; $this->type = $type; $this->feed = $feed; + + $el = $this->feed->documentElement; + + // Looking for the "root" element: RSS channel or Atom feed + + if ($el->tagName == 'rss') { + $channels = $el->getElementsByTagName('channel'); + if ($channels->length > 0) { + $this->root = $channels->item(0); + } else { + throw new FeedSubBadXmlException($sourceurl); + } + } else if ($el->tagName == 'feed') { + $this->root = $el; + } else { + throw new FeedSubBadXmlException($sourceurl); + } + return $this->uri; } else { - throw new FeedSubBadXmlException($url); + throw new FeedSubBadXmlException($sourceurl); } } -- cgit v1.2.3-54-g00ecf From 5092f98c0d7a7db24189aaaf285b6670741fde39 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 19 Mar 2010 15:28:06 -0500 Subject: return empty hints if no hcard in the html --- plugins/OStatus/lib/discoveryhints.php | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/lib/discoveryhints.php b/plugins/OStatus/lib/discoveryhints.php index 1bb0ad2ae..9102788e6 100644 --- a/plugins/OStatus/lib/discoveryhints.php +++ b/plugins/OStatus/lib/discoveryhints.php @@ -65,6 +65,10 @@ class DiscoveryHints { { $hcard = self::_hcard($body, $url); + if (empty($hcard)) { + return array(); + } + $hints = array(); // XXX: don't copy stuff into an array and then copy it again -- cgit v1.2.3-54-g00ecf From db9e57f761b6414e974163e7224d7f04ece291d7 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Fri, 19 Mar 2010 15:50:06 -0500 Subject: ensure from an RSS channel --- plugins/OStatus/classes/Ostatus_profile.php | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index e0e0223b8..80b980aba 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -788,9 +788,20 @@ class Ostatus_profile extends Memcached_DataObject throw new FeedSubNoHubException(); } - // Try to get a profile from the feed activity:subject + $feedEl = $discover->root; + + if ($feedEl->tagName == 'feed') { + return self::ensureAtomFeed($feedEl, $hints); + } else if ($feedEl->tagName == 'channel') { + return self::ensureRssChannel($feedEl, $hints); + } else { + throw new FeedSubBadXmlException($feeduri); + } + } - $feedEl = $discover->feed->documentElement; + public static function ensureAtomFeed($feedEl, $hints) + { + // Try to get a profile from the feed activity:subject $subject = ActivityUtils::child($feedEl, Activity::SUBJECT, Activity::SPEC); @@ -838,6 +849,17 @@ class Ostatus_profile extends Memcached_DataObject throw new FeedSubException("Can't find enough profile information to make a feed."); } + public static function ensureRssChannel($feedEl, $hints) + { + // @fixme we should check whether this feed has elements + // with different or elements, and... I dunno. + // Do something about that. + + $obj = ActivityObject::fromRssChannel($feedEl); + + return self::ensureActivityObjectProfile($obj, $hints); + } + /** * Download and update given avatar image * -- cgit v1.2.3-54-g00ecf From c84c4c6839c1791775cc698ad488bc23ed956d5b Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 19 Mar 2010 15:47:43 -0700 Subject: OStatus: be a little laxer about attempts to start/stop PuSH subscriptions that were left in an inconsistent state. Instead of aborting, we'll try to reconfirm the sub/unsub, which once confirmed will replace whatever the previous state was on the server side. --- plugins/OStatus/classes/FeedSub.php | 6 +++--- plugins/OStatus/classes/Ostatus_profile.php | 21 ++++++++++----------- 2 files changed, 13 insertions(+), 14 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/classes/FeedSub.php b/plugins/OStatus/classes/FeedSub.php index 80ba37bc1..b10509dae 100644 --- a/plugins/OStatus/classes/FeedSub.php +++ b/plugins/OStatus/classes/FeedSub.php @@ -61,7 +61,7 @@ class FeedSub extends Memcached_DataObject public $__table = 'feedsub'; public $id; - public $feeduri; + public $uri; // PuSH subscription data public $huburi; @@ -238,7 +238,7 @@ class FeedSub extends Memcached_DataObject public function subscribe($mode='subscribe') { if ($this->sub_state && $this->sub_state != 'inactive') { - throw new ServerException("Attempting to start PuSH subscription to feed in state $this->sub_state"); + common_log(LOG_WARNING, "Attempting to (re)start PuSH subscription to $this->uri in unexpected state $this->sub_state"); } if (empty($this->huburi)) { if (common_config('feedsub', 'nohub')) { @@ -261,7 +261,7 @@ class FeedSub extends Memcached_DataObject */ public function unsubscribe() { if ($this->sub_state != 'active') { - throw new ServerException("Attempting to end PuSH subscription to feed in state $this->sub_state"); + common_log(LOG_WARNING, "Attempting to (re)end PuSH subscription to $this->uri in unexpected state $this->sub_state"); } if (empty($this->huburi)) { if (common_config('feedsub', 'nohub')) { diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index e0e0223b8..bff6ff209 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -204,12 +204,13 @@ class Ostatus_profile extends Memcached_DataObject public function subscribe() { $feedsub = FeedSub::ensureFeed($this->feeduri); - if ($feedsub->sub_state == 'active' || $feedsub->sub_state == 'subscribe') { + if ($feedsub->sub_state == 'active') { + // Active subscription, we don't need to do anything. return true; - } else if ($feedsub->sub_state == '' || $feedsub->sub_state == 'inactive') { + } else { + // Inactive or we got left in an inconsistent state. + // Run a subscription request to make sure we're current! return $feedsub->subscribe(); - } else if ('unsubscribe') { - throw new FeedSubException("Unsub is pending, can't subscribe..."); } } @@ -222,15 +223,13 @@ class Ostatus_profile extends Memcached_DataObject */ public function unsubscribe() { $feedsub = FeedSub::staticGet('uri', $this->feeduri); - if (!$feedsub) { + if (!$feedsub || $feedsub->sub_state == '' || $feedsub->sub_state == 'inactive') { + // No active PuSH subscription, we can just leave it be. return true; - } - if ($feedsub->sub_state == 'active') { + } else { + // PuSH subscription is either active or in an indeterminate state. + // Send an unsubscribe. return $feedsub->unsubscribe(); - } else if ($feedsub->sub_state == '' || $feedsub->sub_state == 'inactive' || $feedsub->sub_state == 'unsubscribe') { - return true; - } else if ($feedsub->sub_state == 'subscribe') { - throw new FeedSubException("Feed is awaiting subscription, can't unsub..."); } } -- cgit v1.2.3-54-g00ecf From a02e5b302b47cf7a28c2322700a8ec72d31ab22c Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 19 Mar 2010 15:54:16 -0700 Subject: Avoid notices for accessing undefined array indices in hcard processing --- plugins/OStatus/lib/discoveryhints.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/lib/discoveryhints.php b/plugins/OStatus/lib/discoveryhints.php index 9102788e6..80cfbbf15 100644 --- a/plugins/OStatus/lib/discoveryhints.php +++ b/plugins/OStatus/lib/discoveryhints.php @@ -102,7 +102,7 @@ class DiscoveryHints { if (array_key_exists('url', $hcard)) { if (is_string($hcard['url'])) { $hints['homepage'] = $hcard['url']; - } else if (is_array($hcard['url'])) { + } else if (is_array($hcard['url']) && !empty($hcard['url'])) { // HACK get the last one; that's how our hcards look $hints['homepage'] = $hcard['url'][count($hcard['url'])-1]; } @@ -231,7 +231,7 @@ class DiscoveryHints { // If it's got a scheme, use it - if ($parts['scheme'] != '') { + if (!empty($parts['scheme'])) { return $rel; } -- cgit v1.2.3-54-g00ecf From db0cf50f658a91d0d0a019256e6e85d73b7a3ff6 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 19 Mar 2010 15:54:16 -0700 Subject: Avoid notices for accessing undefined array indices in hcard processing --- plugins/OStatus/lib/discoveryhints.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/lib/discoveryhints.php b/plugins/OStatus/lib/discoveryhints.php index 9102788e6..80cfbbf15 100644 --- a/plugins/OStatus/lib/discoveryhints.php +++ b/plugins/OStatus/lib/discoveryhints.php @@ -102,7 +102,7 @@ class DiscoveryHints { if (array_key_exists('url', $hcard)) { if (is_string($hcard['url'])) { $hints['homepage'] = $hcard['url']; - } else if (is_array($hcard['url'])) { + } else if (is_array($hcard['url']) && !empty($hcard['url'])) { // HACK get the last one; that's how our hcards look $hints['homepage'] = $hcard['url'][count($hcard['url'])-1]; } @@ -231,7 +231,7 @@ class DiscoveryHints { // If it's got a scheme, use it - if ($parts['scheme'] != '') { + if (!empty($parts['scheme'])) { return $rel; } -- cgit v1.2.3-54-g00ecf From 51283a1b34b52b22a17cd87f7b23ce384b7b6303 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 20 Mar 2010 06:44:38 -0500 Subject: try to make a nickname from the user profile url before using the URI --- plugins/OStatus/classes/Ostatus_profile.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 80b980aba..31fba009e 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -1322,9 +1322,19 @@ class Ostatus_profile extends Memcached_DataObject return $hints['nickname']; } - // Try the definitive ID + // Try the profile url (like foo.example.com or example.com/user/foo) - $nickname = self::nicknameFromURI($object->id); + $profileUrl = ($object->link) ? $object->link : $hints['profileurl']; + + if (!empty($profileUrl)) { + $nickname = self::nicknameFromURI($profileUrl); + } + + // Try the URI (may be a tag:, http:, acct:, ... + + if (empty($nickname)) { + $nickname = self::nicknameFromURI($object->id); + } // Try a Webfinger if one was passed (way) down -- cgit v1.2.3-54-g00ecf From f55850878450b27b00bd18b140f2be1357d9713c Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 20 Mar 2010 07:23:13 -0500 Subject: handle RSS as well as Atom in Ostatus push hits --- plugins/OStatus/classes/Ostatus_profile.php | 32 ++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 4f449a44d..6885bb953 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -388,11 +388,17 @@ class Ostatus_profile extends Memcached_DataObject { $feed = $doc->documentElement; - if ($feed->localName != 'feed' || $feed->namespaceURI != Activity::ATOM) { - common_log(LOG_ERR, __METHOD__ . ": not an Atom feed, ignoring"); - return; + if ($feed->localName == 'feed' && $feed->namespaceURI == Activity::ATOM) { + $this->processAtomFeed($feed, $source); + } else if ($feed->localName == 'rss') { // @fixme check namespace + $this->processRssFeed($feed, $source); + } else { + throw new Exception("Unknown feed format."); } + } + public function processAtomFeed(DOMElement $feed, $source) + { $entries = $feed->getElementsByTagNameNS(Activity::ATOM, 'entry'); if ($entries->length == 0) { common_log(LOG_ERR, __METHOD__ . ": no entries in feed update, ignoring"); @@ -405,6 +411,26 @@ class Ostatus_profile extends Memcached_DataObject } } + public function processRssFeed(DOMElement $rss, $source) + { + $channels = $rss->getElementsByTagName('channel'); + + if ($channels->length == 0) { + throw new Exception("RSS feed without a channel."); + } else if ($channels->length > 1) { + common_log(LOG_WARNING, __METHOD__ . ": more than one channel in an RSS feed"); + } + + $channel = $channels->item(0); + + $items = $channel->getElementsByTagName('item'); + + for ($i = 0; $i < $items->length; $i++) { + $item = $items->item($i); + $this->processEntry($item, $channel, $source); + } + } + /** * Process a posted entry from this feed source. * -- cgit v1.2.3-54-g00ecf From 25cb9175231f1515c357035c797cb25ec0b01b44 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 20 Mar 2010 08:25:56 -0500 Subject: Allow PuSH posts without author information Superfeedr (sp.?) posts entries without author information. We can assume that this is intended to be by the original author. Re-structured the checks for entries that come in by PuSH so they can either have no author or an empty author, but not a different author. --- plugins/OStatus/classes/Ostatus_profile.php | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 6885bb953..79e20adbd 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -468,17 +468,16 @@ class Ostatus_profile extends Memcached_DataObject return false; } } else { - // Individual user feeds may contain only posts from themselves. - // Authorship is validated against the profile URI on upper layers, - // through PuSH setup or Salmon signature checks. - $actorUri = self::getActorProfileURI($activity); - if ($actorUri == $this->uri) { - // Check if profile info has changed and update it - $this->updateFromActivityObject($activity->actor); + $actor = $activity->actor; + + if (empty($actor)) { + // OK here! assume the default + } else if ($actor->id == $this->uri || $actor->link == $this->uri) { + $this->updateFromActivityObject($actor); } else { - common_log(LOG_WARNING, "OStatus: skipping post with bad author: got $actorUri expected $this->uri"); - return false; + throw new Exception("Got an actor '{$actor->title}' ({$actor->id}) on single-user feed for {$this->uri}"); } + $oprofile = $this; } -- cgit v1.2.3-54-g00ecf From 515acb851384ff063cd7e1088d91798e82af8db8 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 20 Mar 2010 09:30:57 -0500 Subject: fall back to summary or title if content not available --- plugins/OStatus/classes/Ostatus_profile.php | 34 ++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 8 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 79e20adbd..bc8d37dc6 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -481,10 +481,14 @@ class Ostatus_profile extends Memcached_DataObject $oprofile = $this; } + // It's not always an ActivityObject::NOTE, but... let's just say it is. + + $note = $activity->object; + // The id URI will be used as a unique identifier for for the notice, // protecting against duplicate saves. It isn't required to be a URL; // tag: URIs for instance are found in Google Buzz feeds. - $sourceUri = $activity->object->id; + $sourceUri = $note->id; $dupe = Notice::staticGet('uri', $sourceUri); if ($dupe) { common_log(LOG_INFO, "OStatus: ignoring duplicate post: $sourceUri"); @@ -493,16 +497,30 @@ class Ostatus_profile extends Memcached_DataObject // We'll also want to save a web link to the original notice, if provided. $sourceUrl = null; - if ($activity->object->link) { - $sourceUrl = $activity->object->link; + if ($note->link) { + $sourceUrl = $note->link; } else if ($activity->link) { $sourceUrl = $activity->link; - } else if (preg_match('!^https?://!', $activity->object->id)) { - $sourceUrl = $activity->object->id; + } else if (preg_match('!^https?://!', $note->id)) { + $sourceUrl = $note->id; + } + + // Use summary as fallback for content + + if (!empty($note->content)) { + $sourceContent = $note->content; + } else if (!empty($note->summary)) { + $sourceContent = $note->summary; + } else if (!empty($note->title)) { + $sourceContent = $note->title; + } else { + // @fixme fetch from $sourceUrl? + throw new ClientException("No content for notice {$sourceUri}"); } // Get (safe!) HTML and text versions of the content - $rendered = $this->purify($activity->object->content); + + $rendered = $this->purify($sourceContent); $content = html_entity_decode(strip_tags($rendered)); $shortened = common_shorten_links($content); @@ -513,8 +531,8 @@ class Ostatus_profile extends Memcached_DataObject $attachment = null; if (Notice::contentTooLong($shortened)) { - $attachment = $this->saveHTMLFile($activity->object->title, $rendered); - $summary = $activity->object->summary; + $attachment = $this->saveHTMLFile($note->title, $rendered); + $summary = html_entity_decode(strip_tags($note->summary)); if (empty($summary)) { $summary = $content; } -- cgit v1.2.3-54-g00ecf From fb2b45c68abbf48dcdfea330163d17ec56f479bc Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 20 Mar 2010 09:46:22 -0500 Subject: use feedEl for discovery --- plugins/OStatus/classes/Ostatus_profile.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index bc8d37dc6..efb12a2dd 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -864,7 +864,7 @@ class Ostatus_profile extends Memcached_DataObject // Sheesh. Not a very nice feed! Let's try fingerpoken in the // entries. - $entries = $discover->feed->getElementsByTagNameNS(Activity::ATOM, 'entry'); + $entries = $feedEl->getElementsByTagNameNS(Activity::ATOM, 'entry'); if (!empty($entries) && $entries->length > 0) { -- cgit v1.2.3-54-g00ecf From c2afdfbbf5534474d0c5289e7916e77a612aadfd Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 20 Mar 2010 17:18:55 -0500 Subject: use Posterous element if available for RssChannel discovery --- plugins/OStatus/classes/Ostatus_profile.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index efb12a2dd..1c110ab24 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -893,6 +893,20 @@ class Ostatus_profile extends Memcached_DataObject public static function ensureRssChannel($feedEl, $hints) { + // Special-case for Posterous. They have some nice metadata in their + // posterous:author elements. We should use them instead of the channel. + + $items = $feedEl->getElementsByTagName('item'); + + if ($items->length > 0) { + $item = $items->item(0); + $authorEl = ActivityUtils::child($item, ActivityObject::AUTHOR, ActivityObject::POSTEROUS); + if (!empty($authorEl)) { + $obj = ActivityObject::fromPosterousAuthor($authorEl); + return self::ensureActivityObjectProfile($obj, $hints); + } + } + // @fixme we should check whether this feed has elements // with different or elements, and... I dunno. // Do something about that. -- cgit v1.2.3-54-g00ecf From 0f1f7ab79bad2392e10f2bc0d310f5f77b05c531 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 21 Mar 2010 07:37:58 -0500 Subject: only use Posterous author data if it matches the profile URL --- plugins/OStatus/classes/Ostatus_profile.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 1c110ab24..e48ed6ee8 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -903,7 +903,13 @@ class Ostatus_profile extends Memcached_DataObject $authorEl = ActivityUtils::child($item, ActivityObject::AUTHOR, ActivityObject::POSTEROUS); if (!empty($authorEl)) { $obj = ActivityObject::fromPosterousAuthor($authorEl); - return self::ensureActivityObjectProfile($obj, $hints); + // Posterous has multiple authors per feed, and multiple feeds + // per author. We check if this is the "main" feed for this author. + if (array_key_exists('profileurl', $hints) && + !empty($obj->poco) && + common_url_to_nickname($hints['profileurl']) == $obj->poco->preferredUsername) { + return self::ensureActivityObjectProfile($obj, $hints); + } } } -- cgit v1.2.3-54-g00ecf From 5d3bce49b896c1b13453e1b5673d4a5abf2bcdd1 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Sun, 21 Mar 2010 15:18:37 -0700 Subject: OStatus profile setup cleanup * drop OStatusPlugin::localProfileFromUrl(), we can just look up on user.uri * clean up a few edge cases that returned null through Ostatus_profile::ensure* code paths, now throws clear exception when we can't find a feed from the given profile url * add some doc comments on the ensure* methods --- plugins/OStatus/OStatusPlugin.php | 19 -------- plugins/OStatus/classes/Ostatus_profile.php | 76 +++++++++++++++++++++++++++-- 2 files changed, 72 insertions(+), 23 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index 58f373e45..c985fb4bc 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -947,23 +947,4 @@ class OStatusPlugin extends Plugin } return false; } - - /** - * Utility function to check if the given URL is a canonical user profile - * page, and if so return the ID number. - * - * @param string $url - * @return mixed int or false - */ - public static function localProfileFromUrl($url) - { - $template = common_local_url('userbyid', array('id' => '31337')); - $template = preg_quote($template, '/'); - $template = str_replace('31337', '(\d+)', $template); - if (preg_match("/$template/", $url, $matches)) { - return intval($matches[1]); - } - return false; - } - } diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index e48ed6ee8..4ee1a86b4 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -700,9 +700,13 @@ class Ostatus_profile extends Memcached_DataObject } /** + * Look up and if necessary create an Ostatus_profile for the remote entity + * with the given profile page URL. This should never return null -- you + * will either get an object or an exception will be thrown. + * * @param string $profile_url * @return Ostatus_profile - * @throws FeedSubException + * @throws Exception */ public static function ensureProfileURL($profile_url, $hints=array()) @@ -723,7 +727,7 @@ class Ostatus_profile extends Memcached_DataObject $response = $client->get($profile_url); if (!$response->isOk()) { - return null; + throw new Exception("Could not reach profile page: " . $profile_url); } // Check if we have a non-canonical URL @@ -777,11 +781,20 @@ class Ostatus_profile extends Memcached_DataObject if (!empty($feedurl)) { $hints['feedurl'] = $feedurl; - return self::ensureFeedURL($feedurl, $hints); } + + throw new Exception("Could not find a feed URL for profile page " . $finalUrl); } + /** + * Look up the Ostatus_profile, if present, for a remote entity with the + * given profile page URL. Will return null for both unknown and invalid + * remote profiles. + * + * @return mixed Ostatus_profile or null + * @throws Exception for local profiles + */ static function getFromProfileURL($profile_url) { $profile = Profile::staticGet('profileurl', $profile_url); @@ -813,6 +826,14 @@ class Ostatus_profile extends Memcached_DataObject return null; } + /** + * Look up and if necessary create an Ostatus_profile for remote entity + * with the given update feed. This should never return null -- you will + * either get an object or an exception will be thrown. + * + * @return Ostatus_profile + * @throws Exception + */ public static function ensureFeedURL($feed_url, $hints=array()) { $discover = new FeedDiscovery(); @@ -841,6 +862,18 @@ class Ostatus_profile extends Memcached_DataObject } } + /** + * Look up and, if necessary, create an Ostatus_profile for the remote + * profile with the given Atom feed - actually loaded from the feed. + * This should never return null -- you will either get an object or + * an exception will be thrown. + * + * @param DOMElement $feedEl root element of a loaded Atom feed + * @param array $hints additional discovery information passed from higher levels + * @fixme should this be marked public? + * @return Ostatus_profile + * @throws Exception + */ public static function ensureAtomFeed($feedEl, $hints) { // Try to get a profile from the feed activity:subject @@ -891,6 +924,18 @@ class Ostatus_profile extends Memcached_DataObject throw new FeedSubException("Can't find enough profile information to make a feed."); } + /** + * Look up and, if necessary, create an Ostatus_profile for the remote + * profile with the given RSS feed - actually loaded from the feed. + * This should never return null -- you will either get an object or + * an exception will be thrown. + * + * @param DOMElement $feedEl root element of a loaded RSS feed + * @param array $hints additional discovery information passed from higher levels + * @fixme should this be marked public? + * @return Ostatus_profile + * @throws Exception + */ public static function ensureRssChannel($feedEl, $hints) { // Special-case for Posterous. They have some nice metadata in their @@ -1054,11 +1099,14 @@ class Ostatus_profile extends Memcached_DataObject /** * Fetch, or build if necessary, an Ostatus_profile for the actor * in a given Activity Streams activity. + * This should never return null -- you will either get an object or + * an exception will be thrown. * * @param Activity $activity * @param string $feeduri if we already know the canonical feed URI! * @param string $salmonuri if we already know the salmon return channel URI * @return Ostatus_profile + * @throws Exception */ public static function ensureActorProfile($activity, $hints=array()) @@ -1066,6 +1114,18 @@ class Ostatus_profile extends Memcached_DataObject return self::ensureActivityObjectProfile($activity->actor, $hints); } + /** + * Fetch, or build if necessary, an Ostatus_profile for the profile + * in a given Activity Streams object (can be subject, actor, or object). + * This should never return null -- you will either get an object or + * an exception will be thrown. + * + * @param ActivityObject $object + * @param array $hints additional discovery information passed from higher levels + * @return Ostatus_profile + * @throws Exception + */ + public static function ensureActivityObjectProfile($object, $hints=array()) { $profile = self::getActivityObjectProfile($object); @@ -1121,6 +1181,8 @@ class Ostatus_profile extends Memcached_DataObject /** * Create local ostatus_profile and profile/user_group entries for * the provided remote user or group. + * This should never return null -- you will either get an object or + * an exception will be thrown. * * @param ActivityObject $object * @param array $hints @@ -1137,7 +1199,8 @@ class Ostatus_profile extends Memcached_DataObject throw new Exception("No profile URI"); } - if (OStatusPlugin::localProfileFromUrl($homeuri)) { + $user = User::staticGet('uri', $homeuri); + if ($user) { throw new Exception("Local user can't be referenced as remote."); } @@ -1437,6 +1500,11 @@ class Ostatus_profile extends Memcached_DataObject } /** + * Look up, and if necessary create, an Ostatus_profile for the remote + * entity with the given webfinger address. + * This should never return null -- you will either get an object or + * an exception will be thrown. + * * @param string $addr webfinger address * @return Ostatus_profile * @throws Exception on error conditions -- cgit v1.2.3-54-g00ecf From b228da628da51337a28ecb1d5f7416717489496c Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Sun, 21 Mar 2010 15:46:28 -0700 Subject: Accept 'tag' and other non-http id URIs in Ostatus_profile::getActivityObjectProfileURI(). (If there's not a valid ID we fall back to the link, which we do still validate as http/s.) --- plugins/OStatus/classes/Ostatus_profile.php | 36 ++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 11 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 4ee1a86b4..5595a9d29 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -1140,35 +1140,49 @@ class Ostatus_profile extends Memcached_DataObject /** * @param Activity $activity * @return mixed matching Ostatus_profile or false if none known + * @throws ServerException if feed info invalid */ public static function getActorProfile($activity) { return self::getActivityObjectProfile($activity->actor); } + /** + * @param ActivityObject $activity + * @return mixed matching Ostatus_profile or false if none known + * @throws ServerException if feed info invalid + */ protected static function getActivityObjectProfile($object) { $uri = self::getActivityObjectProfileURI($object); return Ostatus_profile::staticGet('uri', $uri); } - protected static function getActorProfileURI($activity) - { - return self::getActivityObjectProfileURI($activity->actor); - } - /** - * @param Activity $activity + * Get the identifier URI for the remote entity described + * by this ActivityObject. This URI is *not* guaranteed to be + * a resolvable HTTP/HTTPS URL. + * + * @param ActivityObject $object * @return string - * @throws ServerException + * @throws ServerException if feed info invalid */ protected static function getActivityObjectProfileURI($object) { - $opts = array('allowed_schemes' => array('http', 'https')); - if ($object->id && Validate::uri($object->id, $opts)) { - return $object->id; + if ($object->id) { + // Possibly an upstream bug; tag: URIs are rejected unless you + // explicitly ask for them. All other schemes are accepted for + // basic URI validation without asking. + if (Validate::uri($object->id) || + Validate::uri($object->id, array('allowed_scheme' => array('tag')))) { + return $object->id; + } } - if ($object->link && Validate::uri($object->link, $opts)) { + + // If the id is missing or invalid (we've seen feeds mistakenly listing + // things like local usernames in that field) then we'll use the profile + // page link, if valid. + if ($object->link && common_valid_http_url($object->link)) { return $object->link; } throw new ServerException("No author ID URI found"); -- cgit v1.2.3-54-g00ecf From fcb614d0eb1f98bf8704654ed06e1f9d9733d359 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Sun, 21 Mar 2010 16:25:12 -0700 Subject: Pull info as well as when we have an old-style ActivityStreams feed. This fixes subscription setup for Cliqset feeds, which currently have a bogus activity:actor/atom:id but a good atom:author/atom:uri --- lib/activityobject.php | 21 +++++++++++++++++++-- lib/activityutils.php | 22 ++++++++++++++++++++++ plugins/OStatus/classes/Ostatus_profile.php | 6 +----- 3 files changed, 42 insertions(+), 7 deletions(-) (limited to 'plugins/OStatus') diff --git a/lib/activityobject.php b/lib/activityobject.php index e5cea727b..0a358ccab 100644 --- a/lib/activityobject.php +++ b/lib/activityobject.php @@ -156,7 +156,11 @@ class ActivityObject { $this->type = self::PERSON; // XXX: is this fair? $this->title = $this->_childContent($element, self::NAME); - $this->id = $this->_childContent($element, self::URI); + + $id = $this->_childContent($element, self::URI); + if (ActivityUtils::validateUri($id)) { + $this->id = $id; + } if (empty($this->id)) { $email = $this->_childContent($element, self::EMAIL); @@ -169,6 +173,15 @@ class ActivityObject private function _fromAtomEntry($element) { + if ($element->localName == 'actor') { + // Old-fashioned ... + // First pull anything from , then we'll add on top. + $author = ActivityUtils::child($element->parentNode, 'author'); + if ($author) { + $this->_fromAuthor($author); + } + } + $this->type = $this->_childContent($element, Activity::OBJECTTYPE, Activity::SPEC); @@ -176,7 +189,11 @@ class ActivityObject $this->type = ActivityObject::NOTE; } - $this->id = $this->_childContent($element, self::ID); + $id = $this->_childContent($element, self::ID); + if (ActivityUtils::validateUri($id)) { + $this->id = $id; + } + $this->summary = ActivityUtils::childHtmlContent($element, self::SUMMARY); $this->content = ActivityUtils::getContent($element); diff --git a/lib/activityutils.php b/lib/activityutils.php index c85a3db55..a7e99fb11 100644 --- a/lib/activityutils.php +++ b/lib/activityutils.php @@ -240,4 +240,26 @@ class ActivityUtils throw new ClientException(_("Can't handle embedded Base64 content yet.")); } } + + /** + * Is this a valid URI for remote profile/notice identification? + * Does not have to be a resolvable URL. + * @param string $uri + * @return boolean + */ + static function validateUri($uri) + { + if (Validate::uri($uri)) { + return true; + } + + // Possibly an upstream bug; tag: URIs aren't validated properly + // unless you explicitly ask for them. All other schemes are accepted + // for basic URI validation without asking. + if (Validate::uri($uri, array('allowed_scheme' => array('tag')))) { + return true; + } + + return false; + } } diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 5595a9d29..e33509c47 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -1170,11 +1170,7 @@ class Ostatus_profile extends Memcached_DataObject protected static function getActivityObjectProfileURI($object) { if ($object->id) { - // Possibly an upstream bug; tag: URIs are rejected unless you - // explicitly ask for them. All other schemes are accepted for - // basic URI validation without asking. - if (Validate::uri($object->id) || - Validate::uri($object->id, array('allowed_scheme' => array('tag')))) { + if (ActivityUtils::validateUri($object->id)) { return $object->id; } } -- cgit v1.2.3-54-g00ecf From edee1fc09e304616fbce4ad1d1dae6097655c4e7 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 22 Mar 2010 08:17:14 -0400 Subject: ignore unrecognized object types --- plugins/OStatus/classes/Ostatus_profile.php | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index e33509c47..0eb5b8b82 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -442,6 +442,17 @@ class Ostatus_profile extends Memcached_DataObject { $activity = new Activity($entry, $feed); + switch ($activity->object->type) { + case ActivityObject::ARTICLE: + case ActivityObject::BLOGENTRY: + case ActivityObject::NOTE: + case ActivityObject::STATUS: + case ActivityObject::COMMENT: + break; + default: + throw new ClientException("Can't handle that kind of post."); + } + if ($activity->verb == ActivityVerb::POST) { $this->processPost($activity, $source); } else { -- cgit v1.2.3-54-g00ecf From a20880ee1e526efafd89ad9b823089f71245c481 Mon Sep 17 00:00:00 2001 From: James Walker Date: Mon, 22 Mar 2010 13:44:05 -0400 Subject: Fixing HTTP Header LRDD parsing (sites in subdirectories need this) --- plugins/OStatus/lib/discovery.php | 2 +- plugins/OStatus/lib/linkheader.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/lib/discovery.php b/plugins/OStatus/lib/discovery.php index 44fad62fb..7187c1f3e 100644 --- a/plugins/OStatus/lib/discovery.php +++ b/plugins/OStatus/lib/discovery.php @@ -195,7 +195,7 @@ class Discovery_LRDD_Link_Header implements Discovery_LRDD // return false; } - return Discovery_LRDD_Link_Header::parseHeader($link_header); + return array(Discovery_LRDD_Link_Header::parseHeader($link_header)); } protected static function parseHeader($header) diff --git a/plugins/OStatus/lib/linkheader.php b/plugins/OStatus/lib/linkheader.php index afcd66d26..cd78d31ce 100644 --- a/plugins/OStatus/lib/linkheader.php +++ b/plugins/OStatus/lib/linkheader.php @@ -11,7 +11,7 @@ class LinkHeader preg_match('/^<[^>]+>/', $str, $uri_reference); //if (empty($uri_reference)) return; - $this->uri = trim($uri_reference[0], '<>'); + $this->href = trim($uri_reference[0], '<>'); $this->rel = array(); $this->type = null; -- cgit v1.2.3-54-g00ecf From 3bb639699c7a5e7e96c0d048adbe48a3ed486fc9 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 22 Mar 2010 11:27:39 -0700 Subject: Confirm there's actually user and domain portions of acct string before assigning things from output of explode(); avoids notice message when invalid input passed to main/xrd --- plugins/OStatus/actions/userxrd.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/actions/userxrd.php b/plugins/OStatus/actions/userxrd.php index eb80a5ad4..6a6886eb8 100644 --- a/plugins/OStatus/actions/userxrd.php +++ b/plugins/OStatus/actions/userxrd.php @@ -35,9 +35,13 @@ class UserxrdAction extends XrdAction $this->uri = Discovery::normalize($this->uri); if (Discovery::isWebfinger($this->uri)) { - list($nick, $domain) = explode('@', substr(urldecode($this->uri), 5)); - $nick = common_canonical_nickname($nick); - $this->user = User::staticGet('nickname', $nick); + $parts = explode('@', substr(urldecode($this->uri), 5)); + if (count($parts) == 2) { + list($nick, $domain) = $parts; + // @fixme confirm the domain too + $nick = common_canonical_nickname($nick); + $this->user = User::staticGet('nickname', $nick); + } } else { $this->user = User::staticGet('uri', $this->uri); } -- cgit v1.2.3-54-g00ecf From 27bfd1211d64298ee3c3b2d82d7b38ca1e1167ad Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 22 Mar 2010 12:17:45 -0700 Subject: Math_BigInteger doesn't correctly handle serialization/deserialization for a value of 0, which can end up spewing notices to output and otherwise intefering with Salmon signature setup and verification when using memcached. Worked around this with a subclass that fixes the wakeup, used for the stored 0 value in the subclassed Crypt_RSA. --- plugins/OStatus/classes/Magicsig.php | 10 ++++------ plugins/OStatus/lib/safecrypt_rsa.php | 18 ++++++++++++++++++ plugins/OStatus/lib/safemath_biginteger.php | 20 ++++++++++++++++++++ 3 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 plugins/OStatus/lib/safecrypt_rsa.php create mode 100644 plugins/OStatus/lib/safemath_biginteger.php (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/classes/Magicsig.php b/plugins/OStatus/classes/Magicsig.php index 5705ecc11..87c684c93 100644 --- a/plugins/OStatus/classes/Magicsig.php +++ b/plugins/OStatus/classes/Magicsig.php @@ -27,8 +27,6 @@ * @link http://status.net/ */ -require_once 'Crypt/RSA.php'; - class Magicsig extends Memcached_DataObject { @@ -102,16 +100,16 @@ class Magicsig extends Memcached_DataObject public function generate($user_id) { - $rsa = new Crypt_RSA(); + $rsa = new SafeCrypt_RSA(); $keypair = $rsa->createKey(); $rsa->loadKey($keypair['privatekey']); - $this->privateKey = new Crypt_RSA(); + $this->privateKey = new SafeCrypt_RSA(); $this->privateKey->loadKey($keypair['privatekey']); - $this->publicKey = new Crypt_RSA(); + $this->publicKey = new SafeCrypt_RSA(); $this->publicKey->loadKey($keypair['publickey']); $this->user_id = $user_id; @@ -163,7 +161,7 @@ class Magicsig extends Memcached_DataObject { common_log(LOG_DEBUG, "Adding ".$type." key: (".$mod .', '. $exp .")"); - $rsa = new Crypt_RSA(); + $rsa = new SafeCrypt_RSA(); $rsa->signatureMode = CRYPT_RSA_SIGNATURE_PKCS1; $rsa->setHash('sha256'); $rsa->modulus = new Math_BigInteger(base64_url_decode($mod), 256); diff --git a/plugins/OStatus/lib/safecrypt_rsa.php b/plugins/OStatus/lib/safecrypt_rsa.php new file mode 100644 index 000000000..f3aa2c928 --- /dev/null +++ b/plugins/OStatus/lib/safecrypt_rsa.php @@ -0,0 +1,18 @@ +zero = new SafeMath_BigInteger(); + } +} + diff --git a/plugins/OStatus/lib/safemath_biginteger.php b/plugins/OStatus/lib/safemath_biginteger.php new file mode 100644 index 000000000..c05e24d1e --- /dev/null +++ b/plugins/OStatus/lib/safemath_biginteger.php @@ -0,0 +1,20 @@ +hex == '') { + $this->hex = '0'; + } + parent::__wakeup(); + } +} + -- cgit v1.2.3-54-g00ecf