· 8 years ago · Jan 02, 2018, 03:20 PM
1md5(salt+username+ip+salt)
2
3possibilities = 2^128
4possibilities = 3.4 * 10^38
5
6guesses_per_second = servers * guesses
7guesses_per_second = 50,000,000 * 1,000,000,000
8guesses_per_second = 50,000,000,000,000,000
9
10time_to_guess = possibilities / guesses_per_second
11time_to_guess = 3.4e38 / 50,000,000,000,000,000
12time_to_guess = 6,800,000,000,000,000,000,000
13
14215,626,585,489,599 years
15
1647917 times the age of the universe
17
18function onLogin($user) {
19 $token = GenerateRandomToken(); // generate a token, should be 128 - 256 bit
20 storeTokenForUser($user, $token);
21 $cookie = $user . ':' . $token;
22 $mac = hash_hmac('sha256', $cookie, SECRET_KEY);
23 $cookie .= ':' . $mac;
24 setcookie('rememberme', $cookie);
25}
26
27function rememberMe() {
28 $cookie = isset($_COOKIE['rememberme']) ? $_COOKIE['rememberme'] : '';
29 if ($cookie) {
30 list ($user, $token, $mac) = explode(':', $cookie);
31 if (!hash_equals(hash_hmac('sha256', $user . ':' . $token, SECRET_KEY), $mac)) {
32 return false;
33 }
34 $usertoken = fetchTokenByUserName($user);
35 if (hash_equals($usertoken, $token)) {
36 logUserIn($user);
37 }
38 }
39}
40
41/**
42 * A timing safe equals comparison
43 *
44 * To prevent leaking length information, it is important
45 * that user input is always used as the second parameter.
46 *
47 * @param string $safe The internal (safe) value to be checked
48 * @param string $user The user submitted (unsafe) value
49 *
50 * @return boolean True if the two strings are identical.
51 */
52function timingSafeCompare($safe, $user) {
53 if (function_exists('hash_equals')) {
54 return hash_equals($safe, $user); // PHP 5.6
55 }
56 // Prevent issues if string length is 0
57 $safe .= chr(0);
58 $user .= chr(0);
59
60 // mbstring.func_overload can make strlen() return invalid numbers
61 // when operating on raw binary strings; force an 8bit charset here:
62 if (function_exists('mb_strlen')) {
63 $safeLen = mb_strlen($safe, '8bit');
64 $userLen = mb_strlen($user, '8bit');
65 } else {
66 $safeLen = strlen($safe);
67 $userLen = strlen($user);
68 }
69
70 // Set the result to the difference between the lengths
71 $result = $safeLen - $userLen;
72
73 // Note that we ALWAYS iterate over the user-supplied length
74 // This is to prevent leaking length information
75 for ($i = 0; $i < $userLen; $i++) {
76 // Using % here is a trick to prevent notices
77 // It's safe, since if the lengths are different
78 // $result is already non-0
79 $result |= (ord($safe[$i % $safeLen]) ^ ord($user[$i]));
80 }
81
82 // They are only identical strings if $result is exactly 0...
83 return $result === 0;
84}
85
86// Set privateKey
87// This should be saved securely
88$key = 'fc4d57ed55a78de1a7b31e711866ef5a2848442349f52cd470008f6d30d47282';
89$key = pack("H*", $key); // They key is used in binary form
90
91// Am Using Memecahe as Sample Database
92$db = new Memcache();
93$db->addserver("127.0.0.1");
94
95try {
96 // Start Remember Me
97 $rememberMe = new RememberMe($key);
98 $rememberMe->setDB($db); // set example database
99
100 // Check if remember me is present
101 if ($data = $rememberMe->auth()) {
102 printf("Returning User %sn", $data['user']);
103
104 // Limit Acces Level
105 // Disable Change of password and private information etc
106
107 } else {
108 // Sample user
109 $user = "baba";
110
111 // Do normal login
112 $rememberMe->remember($user);
113 printf("New Account %sn", $user);
114 }
115} catch (Exception $e) {
116 printf("#Error %sn", $e->getMessage());
117}
118
119class RememberMe {
120 private $key = null;
121 private $db;
122
123 function __construct($privatekey) {
124 $this->key = $privatekey;
125 }
126
127 public function setDB($db) {
128 $this->db = $db;
129 }
130
131 public function auth() {
132
133 // Check if remeber me cookie is present
134 if (! isset($_COOKIE["auto"]) || empty($_COOKIE["auto"])) {
135 return false;
136 }
137
138 // Decode cookie value
139 if (! $cookie = @json_decode($_COOKIE["auto"], true)) {
140 return false;
141 }
142
143 // Check all parameters
144 if (! (isset($cookie['user']) || isset($cookie['token']) || isset($cookie['signature']))) {
145 return false;
146 }
147
148 $var = $cookie['user'] . $cookie['token'];
149
150 // Check Signature
151 if (! $this->verify($var, $cookie['signature'])) {
152 throw new Exception("Cokies has been tampared with");
153 }
154
155 // Check Database
156 $info = $this->db->get($cookie['user']);
157 if (! $info) {
158 return false; // User must have deleted accout
159 }
160
161 // Check User Data
162 if (! $info = json_decode($info, true)) {
163 throw new Exception("User Data corrupted");
164 }
165
166 // Verify Token
167 if ($info['token'] !== $cookie['token']) {
168 throw new Exception("System Hijacked or User use another browser");
169 }
170
171 /**
172 * Important
173 * To make sure the cookie is always change
174 * reset the Token information
175 */
176
177 $this->remember($info['user']);
178 return $info;
179 }
180
181 public function remember($user) {
182 $cookie = [
183 "user" => $user,
184 "token" => $this->getRand(64),
185 "signature" => null
186 ];
187 $cookie['signature'] = $this->hash($cookie['user'] . $cookie['token']);
188 $encoded = json_encode($cookie);
189
190 // Add User to database
191 $this->db->set($user, $encoded);
192
193 /**
194 * Set Cookies
195 * In production enviroment Use
196 * setcookie("auto", $encoded, time() + $expiration, "/~root/",
197 * "example.com", 1, 1);
198 */
199 setcookie("auto", $encoded); // Sample
200 }
201
202 public function verify($data, $hash) {
203 $rand = substr($hash, 0, 4);
204 return $this->hash($data, $rand) === $hash;
205 }
206
207 private function hash($value, $rand = null) {
208 $rand = $rand === null ? $this->getRand(4) : $rand;
209 return $rand . bin2hex(hash_hmac('sha256', $value . $rand, $this->key, true));
210 }
211
212 private function getRand($length) {
213 switch (true) {
214 case function_exists("mcrypt_create_iv") :
215 $r = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
216 break;
217 case function_exists("openssl_random_pseudo_bytes") :
218 $r = openssl_random_pseudo_bytes($length);
219 break;
220 case is_readable('/dev/urandom') : // deceze
221 $r = file_get_contents('/dev/urandom', false, null, 0, $length);
222 break;
223 default :
224 $i = 0;
225 $r = "";
226 while($i ++ < $length) {
227 $r .= chr(mt_rand(0, 255));
228 }
229 break;
230 }
231 return substr(bin2hex($r), 0, $length);
232 }
233}
234
235TABLE users:
236UID - auto increment, PK
237username - varchar(255), unique, indexed, NOT NULL
238password_hash - varchar(255), NOT NULL
239...
240
241TABLE sessions:
242session_id - varchar(?), PK
243session_token - varchar(?), NOT NULL
244session_data - MediumText, NOT NULL
245
246TABLE autologin:
247UID - auto increment, PK
248username - varchar(255), NOT NULL, allow duplicates
249hostname - varchar(255), NOT NULL, allow duplicates
250mac_address - char(23), NOT NULL, unique
251token - varchar(?), NOT NULL, allow duplicates
252expires - datetime code
253
254$data = (SALT + ":" + hash(User Agent) + ":" + username
255 + ":" + LoginTimestamp + ":"+ SALT)