Initialer Import
[memberdb.git] / include / cryptography.php
1 <?php
2
3 /* CRYPTOGRAPHY DOCUMENTATION {{{
4
5 Derived from http://clemens.endorphin.org/TKS1-draft.pdf
6
7 1. Initial setup
8     1.1 Ask for password1
9     1.2 Generate random bytes (salt1)
10     1.3 Generate random bytes (salt2)
11     1.4 Generate random bytes (secret)
12     1.6 Create userkey using PBKDF2 with password1, salt1 (1000 iterations)
13     1.5 Create masterkey using PBKDF2 with secret, salt2 (1000 iterations)
14     1.7 Encrypt masterkey using AES with userkey
15     1.8 Create sha1 hash of masterkey+userkey1+salt1
16     1.9 Store data: key=1.7 salt=1.2, control=1.8
17     
18 2. Generate new key
19     2.1 Ask for password1 (existing)
20     2.2 Ask for password2 (new one)
21     2.3 iterate through valid keys
22         2.3.1 Create userkey1 using PBKDF2 with password1, salt1 (from database) - (1000 iterations)
23         2.3.2 Decrypt key1 (from database) using AES with userkey1
24         2.3.3 Create sha1 hash of masterkey+userkey1+salt1, afterwards compare with control1
25         2.3.4 if equal we have the correct row and the correct master key
26     2.4 Generate random bytes (salt2)
27     2.5 Create userkey2 using PBKDF2 with password2, salt2 (1000 iterations)
28     2.6 Encrypt masterkey (from 2.3) using AES with userkey2
29     2.7 Create sha1 hash of masterkey+userkey2+salt2
30     2.8 Store data: key=2.6 salt=2.4, control=2.7
31
32 3. Delete key
33     3.1 Ask for password
34     3.2 iterate through valid keys
35         3.2.1 Create userkey1 using PBKDF2 with password, salt (from database) - (1000 iterations)
36         3.2.2 Decrypt key (from database) using AES with userkey
37         3.2.3 Create sha1 hash of masterkey+userkey+salt, afterwards compare with control
38         3.2.4 if equal we have the correct row and the correct master key
39         3.2.5 delete the row
40
41 4. Encrypt/Decrypt data
42     4.1 Ask for password
43     4.2 iterate through valid keys
44         4.2.1 Create userkey using PBKDF2 with password, salt (from database) - (1000 iterations)
45         4.2.2 Decrypt key (from database) using AES with userkey
46         4.2.3 Create sha1 hash of masterkey+userkey+salt, afterwards compare with control
47         4.2.4 if equal we have the correct row and the correct master key
48     4.2 Encrypt/Decrypt data using AES with masterkey
49     
50
51 PROBLEM: AES uses a random initialization vector and creates different output for same parameters
52
53 }}} */
54
55
56 $SQL_CREATE_CRYPTO = sprintf('
57     CREATE TABLE IF NOT EXISTS `%1$s` (
58       `description` varchar(255) NOT NULL,
59       `masterkey` blob NOT NULL,
60       `salt` blob NOT NULL,
61       `control` blob NOT NULL,
62       `created_at` datetime NOT NULL,
63       `modified_at` datetime NOT NULL
64     ) ENGINE=InnoDB DEFAULT CHARSET=%2$s
65     ', DB_TABLE_CRYPTO, DB_CHARSET);
66
67 /* DB functions {{{ */
68 function db_get_crypto_keys() {
69     $sql = 'SELECT * FROM %1$s';
70     return db_select_multi(sprintf($sql, DB_TABLE_CRYPTO));
71 }
72
73 function db_add_crypto_key($key) {
74     $key['created_at']    = db_unixtime2datetime(time());
75     $key['modified_at']   = db_unixtime2datetime(time());
76     return (db_insert_single(DB_TABLE_CRYPTO, $key));
77 }
78
79 /* }}} */
80
81 function validate_crypto_key($userdata, &$dbdata, &$validation) {/*{{{*/
82     $fields = array(
83         'description'   => 'string',
84         'masterkey'     => 'string',
85         'salt'          => 'string',
86         'control'       => 'string'
87     );
88     $orig = $dbdata;
89     foreach ($fields as $name => $type) {
90         if (!isset($userdata[$name])) continue;
91         $dbdata[$name] = $userdata[$name];
92     }
93     return true;
94 }/*}}}*/
95
96
97 function action_cryptography() {/*{{{*/
98
99     $log_messages = array();
100     
101     if (isset($_POST['task'])) {
102         switch ($_POST['task']) {
103             case 'add':
104                 $key = array(
105                     'description' => '',
106                     'masterkey'   => '',
107                     'salt'        => '',
108                     'control'     => ''
109                 );
110                 if (validate_crypto_key($_POST, $key, $log_messages)) {
111                     if (db_add_crypto_key($key)) {
112                         redirect(link_to('cryptography'));
113                     }
114                     echo db_error();
115                 }
116                 
117                 break;
118             default:
119                 break;
120         }
121     }
122
123     $keys = db_get_crypto_keys();
124
125 ?>
126     <h2>Liste der Schl&uuml;ssel</h2>
127     <?php if (isset($keys)) : ?>
128         <table>
129             <tr>
130                 <th>Nickname</th>
131             </tr>
132         <?php foreach ($keys as $key) : ?>
133             <tr>
134                 <td><?=html_escape($key['description'])?></td>
135             </tr>
136         <?php endforeach ?>
137         </table>
138     <?php else : ?>
139         <p>Bisher gibt's noch keine Schl&uuml;ssel.</p>
140     <?php endif ?>
141 <?php
142
143     form_cryptography($keys, $log_messages);
144 }/*}}}*/
145
146
147
148 function form_cryptography($keys = array(), $log_messages = array()) {/*{{{*/
149 ?>
150 <?php if (empty($keys)) : ?>
151 <div class="clearfix">
152     <noscript>
153         <p class="error">
154             <strong>Achtung:</strong> Ohne JavaScript geht hier gar nichts. Bitte aktivieren!
155         </p>
156     </noscript>
157     <fieldset class="clearfix">
158         <legend>Ersteinrichtung</legend>
159         <?php log_messages($log_messages); ?>
160         <?=html_text_field('Nickname', 'description_visible')?>
161         <?=html_password_field('Passwort', 'password1')?>
162         <?=html_password_field('Passwort (wdh.)', 'password2')?>
163     </fieldset>
164     <input class="submit" type="button" name="btn_initialize" value="Masterkey erzeugen und speichern" onclick="initialize()"/>
165 </div>
166 <form action="" method="post" id="keyform">
167     <?=html_hidden_field('description')?>
168     <?=html_hidden_field('masterkey')?>
169     <?=html_hidden_field('salt')?>
170     <?=html_hidden_field('control')?>
171     <?=html_hidden_field('task', 'add')?>
172 </form>
173
174 <?php js_modal_windows() ?>
175
176 <script type="text/javascript">
177
178 $(document).ready(function(){  
179     $('#password2').keypress( function(event) { if (event.keyCode == '13') { initialize(); } });
180 });  
181
182 function initialize() {/*{{{*/
183 //    var t1 = new Date().getTime()
184
185     var description = $('#description_visible').val();
186     if (description.length < 1) {
187         alert('Bitte einen Nickname angeben.');
188         return;
189     }
190
191     // 1.1 Ask for password1
192     // password should be already entered into the two input fields
193     var password1 = $('#password1').val();
194     var password2 = $('#password2').val();
195     if (password1 != password2) {
196         alert('Die Passw├Ârter sind nicht identisch.');
197         return;
198     }
199     if (password1.length < 8) {
200         alert('Das Passwort ist zu kurz (min. 8 Zeichen).');
201         return;
202     }
203
204     modal_window_show($('#please_wait'));
205     $('#password1').val('');
206     $('#password2').val('');
207
208     setTimeout(function() {
209         // 1.2 Generate random bytes (salt1)
210         var salt1       = Crypto.charenc.Binary.bytesToString(Crypto.util.randomBytes(32));
211
212         // 1.3 Generate random bytes (salt2)
213         var salt2       = Crypto.charenc.Binary.bytesToString(Crypto.util.randomBytes(32));
214
215         // 1.4 Generate random bytes (secret)
216         var secret      = Crypto.charenc.Binary.bytesToString(Crypto.util.randomBytes(32));
217
218         // 1.5 Create masterkey using PBKDF2 with secret, salt2 (1000 iterations)
219         var masterkey   = Crypto.PBKDF2(secret,    salt2, 256, { iterations: 1000, asBytes: true });
220         secret          = ''; // we don't need this anymore
221         salt2           = ''; // we don't need this anymore
222
223         // 1.6 Create userkey using PBKDF2 with password1, salt1 (1000 iterations)
224         var userkey     = Crypto.PBKDF2(password1, salt1, 256, { iterations: 1000, asBytes: true });
225         password1       = ''; // we don't need this anymore
226
227         // 1.7 Encrypt masterkey using AES with userkey
228         var crypted_masterkey = Crypto.AES.encrypt(masterkey, userkey);
229
230         // 1.8 Create sha1 hash of masterkey+userkey+salt
231         var control     = Crypto.SHA1(Crypto.charenc.Binary.bytesToString(masterkey) + Crypto.charenc.Binary.bytesToString(userkey) + salt1);
232
233     /* XXX DEBUG XXX
234         alert(
235             "masterkey: " + Crypto.charenc.Binary.bytesToString(masterkey).length + " " + masterkey + "\n" +
236             "userkey: " + userkey + "\n" + 
237             "control: " + control
238         );
239         return;
240     */
241     //    var t2 = new Date().getTime()
242     //    alert((t2 - t1) / 1000); // benchmark
243
244         // 1.9 Store data: key=1.7 salt=1.2, control=1.8
245         $('#masterkey').val(crypted_masterkey);
246         $('#salt').val(Crypto.util.bytesToHex(Crypto.charenc.Binary.stringToBytes(salt1)));
247         $('#control').val(control);
248         $('#description').val(description);
249
250         $('#keyform').submit();
251     }, 500);
252 }/*}}}*/
253 </script>
254
255 <?php else : ?>
256
257 <div class="clearfix">
258     <noscript>
259         <p class="error">
260             <strong>Achtung:</strong> Ohne JavaScript geht hier gar nichts. Bitte aktivieren!
261         </p>
262     </noscript>
263     <fieldset class="clearfix">
264         <legend>Passwort hinzuf&uuml;gen</legend>
265         <?php log_messages($log_messages); ?>
266         <?=html_password_field('Exist. Passwort', 'password1')?>
267         <small>Bestehendes Passwort</small>
268         <?=html_text_field('Nickname', 'description_visible')?>
269         <?=html_password_field('Passwort', 'password2')?>
270         <?=html_password_field('Passwort (wdh.)', 'password3')?>
271     </fieldset>
272     <input class="submit" type="button" name="btn_add_new_key" value="Passwort hinzuf&uuml;gen" onclick="add_new_key()"/>
273 </div>
274 <form action="" method="post" id="keyform">
275     <?=html_hidden_field('description')?>
276     <?=html_hidden_field('masterkey')?>
277     <?=html_hidden_field('salt')?>
278     <?=html_hidden_field('control')?>
279     <?=html_hidden_field('task', 'add')?>
280 </form>
281
282 <?php js_modal_windows() ?>
283 <?php js_get_master_key() ?>
284
285 <script type="text/javascript">
286
287 $(document).ready(function(){  
288     $('#password3').keypress( function(event) { if (event.keyCode == '13') { add_new_key(); } });
289 });  
290
291 function add_new_key() {/*{{{*/
292
293     var description = $('#description_visible').val();
294     if (description.length < 1) {
295         alert('Bitte einen Nickname angeben.');
296         return;
297     }
298
299     // 2.1 Check password1 (existing)
300     var password1 = $('#password1').val();
301     if (password1.length < 1) {
302         alert('Bitte ein existierenges Passwort angeben.');
303         return;
304     }
305
306     // 2.2 Check password2 (new one)
307     var password2 = $('#password2').val();
308     var password3 = $('#password3').val();
309     if (password2 != password3) {
310         alert('Die Passw├Ârter sind nicht identisch.');
311         return;
312     }
313     if (password2.length < 8) {
314         alert('Das Passwort ist zu kurz (min. 8 Zeichen).');
315         return;
316     }
317
318     get_master_key(password1, function(masterkey) {
319
320         $('#password1').val('');
321         $('#password2').val('');
322         $('#password3').val('');
323
324         // 2.4 Generate random bytes (salt2)
325         var salt2       = Crypto.charenc.Binary.bytesToString(Crypto.util.randomBytes(32));
326
327         // 2.5 Create userkey2 using PBKDF2 with password2, salt2 (1000 iterations)
328         var userkey2    = Crypto.PBKDF2(password2, salt2, 256, { iterations: 1000, asBytes: true });
329
330         // 2.6 Encrypt masterkey (from 2.3) using AES with userkey2
331         var crypted_masterkey = Crypto.AES.encrypt(masterkey, userkey2);
332
333         // 2.7 Create sha1 hash of masterkey+userkey2+salt2
334         var control     = Crypto.SHA1(Crypto.charenc.Binary.bytesToString(masterkey) + Crypto.charenc.Binary.bytesToString(userkey2) + salt2);
335
336         // 2.8 Store data: key=2.6 salt=2.4, control=2.7
337         $('#masterkey').val(crypted_masterkey);
338         $('#salt').val(Crypto.util.bytesToHex(Crypto.charenc.Binary.stringToBytes(salt2)));
339         $('#control').val(control);
340         $('#description').val(description);
341
342         $('#keyform').submit();
343     }, 10);
344 }/*}}}*/
345 </script>
346 <?php endif ?>
347
348 <?php
349 }/*}}}*/
350
351 function js_get_master_key($keys = NULL) {/*{{{*/
352     if (!isset($keys)) $keys = db_get_crypto_keys();
353     if (empty($keys)) return;
354 ?>
355 <script type="text/javascript">
356
357 var keys = new Array(
358     <?=join(",\n", array_map('json_encode', $keys)) ?>
359 );
360
361 function get_master_key(password, callback) {/*{{{*/
362
363     modal_window_show($("#please_wait"));
364
365     // 4.2 iterate through valid keys
366     setTimeout(function() { check_master_key(0, password, callback); }, 1000);
367
368 }/*}}}*/
369
370 function check_master_key(idx, password, callback) {/*{{{*/
371
372     if (idx >= keys.length) {
373         modal_window_hide();
374         alert("Falsches Passwort");
375         return;
376     }
377
378     var crypted_masterkey   = keys[idx]['masterkey'];
379     var salt                = Crypto.charenc.Binary.bytesToString(Crypto.util.hexToBytes(keys[idx]['salt']));
380     var control1            = keys[idx]['control'];
381
382     // 1 Create userkey using PBKDF2 with password, salt (from database) - (1000 iterations)
383     var userkey             = Crypto.PBKDF2(password, salt, 256, { iterations: 1000, asBytes: true });
384
385     // 2 Decrypt key (from database) using AES with userkey
386     var masterkey           = Crypto.AES.decrypt(crypted_masterkey, userkey);
387     // 3 Create sha1 hash of masterkey+userkey+salt, afterwards compare with control
388     var control2            = Crypto.SHA1(Crypto.charenc.Binary.bytesToString(masterkey) + Crypto.charenc.Binary.bytesToString(userkey) + salt);
389
390     // 4 if equal we have the correct row and the correct master key
391     if (control1 == control2) {
392         modal_window_hide();
393         callback(masterkey);
394         return;
395     }
396
397     // check next key
398     setTimeout( function() { check_master_key(idx + 1, password, callback); }, 10);
399
400 }/*}}}*/
401
402 </script>
403 <?php
404 }/*}}}*/
405
406