Browse Source

2FA Changes

- Default interval now 30 seconds (breaking!)
- Don't add default parameters to QR code (smaller)

Fixes #22
spaghetti 8 years ago
parent
commit
f1e0c93d42
2 changed files with 22 additions and 49 deletions
  1. 21
    48
      classes/twofa.class.php
  2. 1
    1
      sections/user/edit.php

+ 21
- 48
classes/twofa.class.php View File

@@ -10,7 +10,7 @@ class TwoFactorAuth {
10 10
   private static $_base32lookup = array();
11 11
   private static $_supportedalgos = array('sha1', 'sha256', 'sha512', 'md5');
12 12
 
13
-  function __construct($issuer = null, $digits = 6, $period = 60, $algorithm = 'sha1') {
13
+  function __construct($issuer = null, $digits = 6, $period = 30, $algorithm = 'sha1') {
14 14
     $this->issuer = $issuer;
15 15
     $this->digits = $digits;
16 16
     $this->period = $period;
@@ -25,21 +25,17 @@ class TwoFactorAuth {
25 25
     self::$_base32lookup = array_flip(self::$_base32);
26 26
   }
27 27
 
28
-  /**
29
-   * Create a new secret
30
-   */
31 28
   public function createSecret($bits = 80) {
32 29
     $secret = '';
33
-    $bytes = ceil($bits / 5);   //We use 5 bits of each byte (since we have a 32-character 'alphabet' / BASE32)
30
+    $bytes = ceil($bits / 5); //We use 5 bits of each byte (since we have a 32-character 'alphabet' / BASE32)
34 31
     $rnd = random_bytes($bytes);
35
-    for ($i = 0; $i < $bytes; $i++)
36
-      $secret .= self::$_base32[ord($rnd[$i]) & 31];  //Mask out left 3 bits for 0-31 values
32
+    for ($i = 0; $i < $bytes; $i++) {
33
+      $secret .= self::$_base32[ord($rnd[$i]) & 31]; //Mask out left 3 bits for 0-31 values
34
+    }
37 35
     return $secret;
38 36
   }
39 37
 
40
-  /**
41
-   * Calculate the code with given secret and point in time
42
-   */
38
+  // Calculate the code with given secret and point in time
43 39
   public function getCode($secret, $time = null) {
44 40
     $secretkey = $this->base32Decode($secret);
45 41
 
@@ -52,42 +48,20 @@ class TwoFactorAuth {
52 48
     return str_pad($value % pow(10, $this->digits), $this->digits, '0', STR_PAD_LEFT);
53 49
   }
54 50
 
55
-  /**
56
-   * Check if the code is correct. This will accept codes starting from ($discrepancy * $period) sec ago to ($discrepancy * period) sec from now
57
-   */
51
+  // Check if the code is correct. This will accept codes starting from now +/- ($discrepancy * period) seconds
58 52
   public function verifyCode($secret, $code, $discrepancy = 1, $time = null) {
59 53
     $result = false;
60 54
     $timetamp = $this->getTime($time);
61 55
 
62
-    // To keep safe from timing-attachs we iterate *all* possible codes even though we already may have verified a code is correct
63
-    for ($i = -$discrepancy; $i <= $discrepancy; $i++)
64
-      $result |= $this->codeEquals($this->getCode($secret, $timetamp + ($i * $this->period)), $code);
56
+    // Always iterate all possible codes to prevent timing-attacks
57
+    for ($i = -$discrepancy; $i <= $discrepancy; $i++) {
58
+      $result |= hash_equals($this->getCode($secret, $timetamp + ($i * $this->period)), $code);
59
+    }
65 60
 
66 61
     return (bool)$result;
67 62
   }
68 63
 
69
-  /**
70
-   * Timing-attack safe comparison of 2 codes (see http://blog.ircmaxell.com/2014/11/its-all-about-time.html)
71
-   */
72
-  private function codeEquals($safe, $user) {
73
-    if (function_exists('hash_equals')) {
74
-      return hash_equals($safe, $user);
75
-    } else {
76
-      // In general, it's not possible to prevent length leaks. So it's OK to leak the length. The important part is that
77
-      // we don't leak information about the difference of the two strings.
78
-      if (strlen($safe)===strlen($user)) {
79
-        $result = 0;
80
-        for ($i = 0; $i < strlen($safe); $i++)
81
-          $result |= (ord($safe[$i]) ^ ord($user[$i]));
82
-        return $result === 0;
83
-      }
84
-    }
85
-    return false;
86
-  }
87
-
88
-  /**
89
-   * Get data-uri of QRCode
90
-   */
64
+  // Get data-uri of QRCode
91 65
   public function getQRCodeImageAsDataUri($label, $secret, $size = 300) {
92 66
 
93 67
     if (exec('which qrencode')) {
@@ -120,16 +94,14 @@ class TwoFactorAuth {
120 94
     return (int)floor($time / $this->period) + ($offset * $this->period);
121 95
   }
122 96
 
123
-  /**
124
-   * Builds a string to be encoded in a QR code
125
-   */
97
+  // Builds a string to be encoded in a QR code
126 98
   public function getQRText($label, $secret) {
127
-    return 'otpauth://totp/' . rawurlencode($label)
128
-      . '?secret=' . rawurlencode($secret)
129
-      . '&issuer=' . rawurlencode($this->issuer)
130
-      . '&period=' . intval($this->period)
131
-      . '&algorithm=' . rawurlencode(strtoupper($this->algorithm))
132
-      . '&digits=' . intval($this->digits);
99
+    $QRText = 'otpauth://totp/'.rawurlencode($label).'?secret='.rawurlencode($secret);
100
+    $QRText .= ($this->issuer) ? '&issuer='.rawurlencode($this->issuer) : '';
101
+    $QRText .= ($this->period != 30) ? '&period='.intval($this->period) : '';
102
+    $QRText .= ($this->algorithm != 'sha1') ? '&algorithm='.rawurlencode(strtoupper($this->algorithm)) : '';
103
+    $QRText .= ($this->digits != 6) ? '&digits='.intval($this->digits) : '';
104
+    return $QRText;
133 105
   }
134 106
 
135 107
   private function base32Decode($value) {
@@ -145,8 +117,9 @@ class TwoFactorAuth {
145 117
     $blocks = trim(chunk_split(substr($buffer, 0, $length - ($length % 8)), 8, ' '));
146 118
 
147 119
     $output = '';
148
-    foreach (explode(' ', $blocks) as $block)
120
+    foreach (explode(' ', $blocks) as $block) {
149 121
       $output .= chr(bindec(str_pad($block, 8, 0, STR_PAD_RIGHT)));
122
+    }
150 123
 
151 124
     return $output;
152 125
   }

+ 1
- 1
sections/user/edit.php View File

@@ -27,7 +27,7 @@ $DB->query("
27 27
   WHERE m.ID = '".db_string($UserID)."'");
28 28
 list($Username, $TwoFactor, $Email, $IRCKey, $Paranoia, $Info, $Avatar, $StyleID, $StyleURL, $SiteOptions, $UnseededAlerts, $DownloadAlt, $Class, $InfoTitle) = $DB->next_record(MYSQLI_NUM, array(4, 9));
29 29
 
30
-$TwoFA = new TwoFactorAuth(SITE_NAME);
30
+$TwoFA = new TwoFactorAuth();
31 31
 
32 32
 $Email = apc_exists('DBKEY') ? DBCrypt::decrypt($Email) : '[Encrypted]';
33 33
 

Loading…
Cancel
Save