New protocol v3 : include pkdbf2 level

Remove hashtable from firefox addon
Rework firefox addon
Add pkdbf2_level as a preference (hidden)
This commit is contained in:
Grégory Soutadé 2014-01-21 19:00:26 +01:00
parent 1ff4a87beb
commit 84eaf0c6a1
8 changed files with 291 additions and 539 deletions

View File

@ -42,10 +42,11 @@ Server
To host a password server, you need a webserver. Just copy server files in a directory read/write for web server user (www-data). A sample apache2 configuration file is available in ressources. For enhanced security, it's better to put the password server under https.
You can activate/deactivate user creation by setting $ADMIN_MODE in index.php.
Configuration parameters are in conf.php
A demonstration server is available [here](http://gpass-demo.soutade.fr). It's the default server of XPI package (user demo).
Warning The master key derivation is partially based on account URL. So it's linked to your current server information. Currently there is no simple way to export/import a full gPass database.
Client
------

View File

@ -1,404 +0,0 @@
/**
* @license jahashtable, a JavaScript implementation of a hash table. It creates a single constructor function called
* Hashtable in the global scope.
*
* http://www.timdown.co.uk/jshashtable/
* Copyright 2013 Tim Down.
* Version: 3.0
* Build date: 17 July 2013
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var Hashtable = (function(UNDEFINED) {
var FUNCTION = "function", STRING = "string", UNDEF = "undefined";
// Require Array.prototype.splice, Object.prototype.hasOwnProperty and encodeURIComponent. In environments not
// having these (e.g. IE <= 5), we bail out now and leave Hashtable null.
if (typeof encodeURIComponent == UNDEF ||
Array.prototype.splice === UNDEFINED ||
Object.prototype.hasOwnProperty === UNDEFINED) {
return null;
}
function toStr(obj) {
return (typeof obj == STRING) ? obj : "" + obj;
}
function hashObject(obj) {
var hashCode;
if (typeof obj == STRING) {
return obj;
} else if (typeof obj.hashCode == FUNCTION) {
// Check the hashCode method really has returned a string
hashCode = obj.hashCode();
return (typeof hashCode == STRING) ? hashCode : hashObject(hashCode);
} else {
return toStr(obj);
}
}
function merge(o1, o2) {
for (var i in o2) {
if (o2.hasOwnProperty(i)) {
o1[i] = o2[i];
}
}
}
function equals_fixedValueHasEquals(fixedValue, variableValue) {
return fixedValue.equals(variableValue);
}
function equals_fixedValueNoEquals(fixedValue, variableValue) {
return (typeof variableValue.equals == FUNCTION) ?
variableValue.equals(fixedValue) : (fixedValue === variableValue);
}
function createKeyValCheck(kvStr) {
return function(kv) {
if (kv === null) {
throw new Error("null is not a valid " + kvStr);
} else if (kv === UNDEFINED) {
throw new Error(kvStr + " must not be undefined");
}
};
}
var checkKey = createKeyValCheck("key"), checkValue = createKeyValCheck("value");
/*----------------------------------------------------------------------------------------------------------------*/
function Bucket(hash, firstKey, firstValue, equalityFunction) {
this[0] = hash;
this.entries = [];
this.addEntry(firstKey, firstValue);
if (equalityFunction !== null) {
this.getEqualityFunction = function() {
return equalityFunction;
};
}
}
var EXISTENCE = 0, ENTRY = 1, ENTRY_INDEX_AND_VALUE = 2;
function createBucketSearcher(mode) {
return function(key) {
var i = this.entries.length, entry, equals = this.getEqualityFunction(key);
while (i--) {
entry = this.entries[i];
if ( equals(key, entry[0]) ) {
switch (mode) {
case EXISTENCE:
return true;
case ENTRY:
return entry;
case ENTRY_INDEX_AND_VALUE:
return [ i, entry[1] ];
}
}
}
return false;
};
}
function createBucketLister(entryProperty) {
return function(aggregatedArr) {
var startIndex = aggregatedArr.length;
for (var i = 0, entries = this.entries, len = entries.length; i < len; ++i) {
aggregatedArr[startIndex + i] = entries[i][entryProperty];
}
};
}
Bucket.prototype = {
getEqualityFunction: function(searchValue) {
return (typeof searchValue.equals == FUNCTION) ? equals_fixedValueHasEquals : equals_fixedValueNoEquals;
},
getEntryForKey: createBucketSearcher(ENTRY),
getEntryAndIndexForKey: createBucketSearcher(ENTRY_INDEX_AND_VALUE),
removeEntryForKey: function(key) {
var result = this.getEntryAndIndexForKey(key);
if (result) {
this.entries.splice(result[0], 1);
return result[1];
}
return null;
},
addEntry: function(key, value) {
this.entries.push( [key, value] );
},
keys: createBucketLister(0),
values: createBucketLister(1),
getEntries: function(destEntries) {
var startIndex = destEntries.length;
for (var i = 0, entries = this.entries, len = entries.length; i < len; ++i) {
// Clone the entry stored in the bucket before adding to array
destEntries[startIndex + i] = entries[i].slice(0);
}
},
containsKey: createBucketSearcher(EXISTENCE),
containsValue: function(value) {
var entries = this.entries, i = entries.length;
while (i--) {
if ( value === entries[i][1] ) {
return true;
}
}
return false;
}
};
/*----------------------------------------------------------------------------------------------------------------*/
// Supporting functions for searching hashtable buckets
function searchBuckets(buckets, hash) {
var i = buckets.length, bucket;
while (i--) {
bucket = buckets[i];
if (hash === bucket[0]) {
return i;
}
}
return null;
}
function getBucketForHash(bucketsByHash, hash) {
var bucket = bucketsByHash[hash];
// Check that this is a genuine bucket and not something inherited from the bucketsByHash's prototype
return ( bucket && (bucket instanceof Bucket) ) ? bucket : null;
}
/*----------------------------------------------------------------------------------------------------------------*/
function Hashtable() {
var buckets = [];
var bucketsByHash = {};
var properties = {
replaceDuplicateKey: true,
hashCode: hashObject,
equals: null
};
var arg0 = arguments[0], arg1 = arguments[1];
if (arg1 !== UNDEFINED) {
properties.hashCode = arg0;
properties.equals = arg1;
} else if (arg0 !== UNDEFINED) {
merge(properties, arg0);
}
var hashCode = properties.hashCode, equals = properties.equals;
this.properties = properties;
this.put = function(key, value) {
checkKey(key);
checkValue(value);
var hash = hashCode(key), bucket, bucketEntry, oldValue = null;
// Check if a bucket exists for the bucket key
bucket = getBucketForHash(bucketsByHash, hash);
if (bucket) {
// Check this bucket to see if it already contains this key
bucketEntry = bucket.getEntryForKey(key);
if (bucketEntry) {
// This bucket entry is the current mapping of key to value, so replace the old value.
// Also, we optionally replace the key so that the latest key is stored.
if (properties.replaceDuplicateKey) {
bucketEntry[0] = key;
}
oldValue = bucketEntry[1];
bucketEntry[1] = value;
} else {
// The bucket does not contain an entry for this key, so add one
bucket.addEntry(key, value);
}
} else {
// No bucket exists for the key, so create one and put our key/value mapping in
bucket = new Bucket(hash, key, value, equals);
buckets.push(bucket);
bucketsByHash[hash] = bucket;
}
return oldValue;
};
this.get = function(key) {
checkKey(key);
var hash = hashCode(key);
// Check if a bucket exists for the bucket key
var bucket = getBucketForHash(bucketsByHash, hash);
if (bucket) {
// Check this bucket to see if it contains this key
var bucketEntry = bucket.getEntryForKey(key);
if (bucketEntry) {
// This bucket entry is the current mapping of key to value, so return the value.
return bucketEntry[1];
}
}
return null;
};
this.containsKey = function(key) {
checkKey(key);
var bucketKey = hashCode(key);
// Check if a bucket exists for the bucket key
var bucket = getBucketForHash(bucketsByHash, bucketKey);
return bucket ? bucket.containsKey(key) : false;
};
this.containsValue = function(value) {
checkValue(value);
var i = buckets.length;
while (i--) {
if (buckets[i].containsValue(value)) {
return true;
}
}
return false;
};
this.clear = function() {
buckets.length = 0;
bucketsByHash = {};
};
this.isEmpty = function() {
return !buckets.length;
};
var createBucketAggregator = function(bucketFuncName) {
return function() {
var aggregated = [], i = buckets.length;
while (i--) {
buckets[i][bucketFuncName](aggregated);
}
return aggregated;
};
};
this.keys = createBucketAggregator("keys");
this.values = createBucketAggregator("values");
this.entries = createBucketAggregator("getEntries");
this.remove = function(key) {
checkKey(key);
var hash = hashCode(key), bucketIndex, oldValue = null;
// Check if a bucket exists for the bucket key
var bucket = getBucketForHash(bucketsByHash, hash);
if (bucket) {
// Remove entry from this bucket for this key
oldValue = bucket.removeEntryForKey(key);
if (oldValue !== null) {
// Entry was removed, so check if bucket is empty
if (bucket.entries.length == 0) {
// Bucket is empty, so remove it from the bucket collections
bucketIndex = searchBuckets(buckets, hash);
buckets.splice(bucketIndex, 1);
delete bucketsByHash[hash];
}
}
}
return oldValue;
};
this.size = function() {
var total = 0, i = buckets.length;
while (i--) {
total += buckets[i].entries.length;
}
return total;
};
}
Hashtable.prototype = {
each: function(callback) {
var entries = this.entries(), i = entries.length, entry;
while (i--) {
entry = entries[i];
callback(entry[0], entry[1]);
}
},
equals: function(hashtable) {
var keys, key, val, count = this.size();
if (count == hashtable.size()) {
keys = this.keys();
while (count--) {
key = keys[count];
val = hashtable.get(key);
if (val === null || val !== this.get(key)) {
return false;
}
}
return true;
}
return false;
},
putAll: function(hashtable, conflictCallback) {
var entries = hashtable.entries();
var entry, key, value, thisValue, i = entries.length;
var hasConflictCallback = (typeof conflictCallback == FUNCTION);
while (i--) {
entry = entries[i];
key = entry[0];
value = entry[1];
// Check for a conflict. The default behaviour is to overwrite the value for an existing key
if ( hasConflictCallback && (thisValue = this.get(key)) ) {
value = conflictCallback(key, thisValue, value);
}
this.put(key, value);
}
},
clone: function() {
var clone = new Hashtable(this.properties);
clone.putAll(this);
return clone;
}
};
Hashtable.prototype.toQueryString = function() {
var entries = this.entries(), i = entries.length, entry;
var parts = [];
while (i--) {
entry = entries[i];
parts[i] = encodeURIComponent( toStr(entry[0]) ) + "=" + encodeURIComponent( toStr(entry[1]) );
}
return parts.join("&");
};
return Hashtable;
})();
exports.Hashtable = Hashtable;

View File

@ -20,14 +20,14 @@
var {Cc, Ci} = require("chrome");
var notifications = require("sdk/notifications");
// http://www.timdown.co.uk/jshashtable/
var Hashtable = require("jshashtable-3.0").Hashtable;
var pkdbf2 = require("pkdbf2").pkdbf2;
var aes = require("jsaes").aes;
var parseURI = require("parseuri").parseURI;
var prefSet = require("simple-prefs");
var prefSet = require("sdk/simple-prefs");
var DEBUG = false;
var pkdbf2_level = prefSet.prefs.pkdbf2_level;
var protocol_version = 3;
SERVER = { OK : 0, FAILED : 1, RESTART_REQUEST : 2};
// http://stackoverflow.com/questions/3745666/how-to-convert-from-hex-to-ascii-in-javascript
function hex2a(hex) {
@ -54,6 +54,185 @@ function debug(s)
console.log(s);
}
function generate_request(domain, login, mkey)
{
v = "@@" + domain + ";" + login;
debug("will encrypt " + v);
debug("with " + a2hex(mkey));
enc = aes.encryptLongString(v, aes.init(mkey));
aes.finish();
debug("res " + enc);
return enc;
}
function ask_server(field, logins, domain, wdomain, mkey, salt)
{
mkey = pkdbf2.pkdbf2(mkey, salt, pkdbf2_level, 256/8);
keys = "";
for(a=0, b=logins.length; a<logins.length; a++)
{
enc = generate_request(domain, logins[a], mkey);
keys += (keys.length != 0) ? "&" : "";
keys += "k" + a + "=" + a2hex(enc);
if (wdomain != "")
{
enc = generate_request(wdomain, logins[a], mkey);
keys += (keys.length != 0) ? "&" : "";
keys += "k" + (b++) + "=" + a2hex(enc);
}
}
debug("Keys " + keys);
// Need to do a synchronous request
var gPassRequest = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
createInstance(Ci.nsIXMLHttpRequest);
var ret = SERVER.OK;
// gPassRequest.addEventListener("progress", function(evt) { ; }, false);
gPassRequest.addEventListener("load", function(evt) {
ciphered_password = "";
server_pkdbf2_level = 0;
server_version = 0;
r = this.responseText.split("\n");
debug("resp " + r);
for(a=0; a<r.length; a++)
{
debug("Analyse " + r[a]);
params = r[a].split("=");
if (params.length != 2 && params[0] != "<end>")
{
notifications.notify({
title: "gPasss",
text: "Error : It seems that it's not a gPass server",
data: this.responseText,
});
ret = SERVER.FAILED;
break;
}
switch(params[0])
{
case "protocol":
debug("protocol : " + params[1]);
if (params[1].indexOf("gpass-") != 0)
{
notifications.notify({
title: "gPasss",
text: "Error : It seems that it's not a gPass server",
data: this.responseText,
});
ret = SERVER.FAILED;
break;
}
server_protocol_version = params[1].match(/\d+/)[0];
if (server_protocol_version > protocol_version)
{
notifications.notify({
title: "gPasss",
text: "Protocol version not supported, please upgrade your addon",
data: "Protocol version not supported, please upgrade your addon",
});
ret = SERVER.FAILED;
}
else
{
switch (server_protocol_version)
{
case 2:
server_pkdbf2_level = 1000;
break;
case 3:
// Version 3 : nothing special to do
break;
}
}
break;
case "pass":
ciphered_password = params[1];
break;
case "pkdbf2_level":
server_pkdbf2_level = parseInt(params[1].match(/\d+/)[0], 10);
if (server_pkdbf2_level != NaN && server_pkdbf2_level != pkdbf2_level)
{
debug("New pkdbf2 level " + server_pkdbf2_level);
pkdbf2_level = server_pkdbf2_level;
prefSet.prefs.pkdbf2_level = server_pkdbf2_level;
ret = SERVER.RESTART_REQUEST;
}
break;
case "<end>":
break;
default:
debug("Unknown command " + params[0]);
notifications.notify({
title: "gPasss",
text: "Error : It seems that it's not a gPass server",
data: this.responseText,
});
ret = SERVER.FAILED;
break;
}
}
if (ret != SERVER.OK)
return;
if (ciphered_password != "")
{
debug("Ciphered password : " + ciphered_password);
clear_password = aes.decryptLongString(hex2a(ciphered_password), aes.init(mkey));
aes.finish();
// Remove trailing \0 and salt
clear_password = clear_password.replace(/\0*$/, "");
clear_password = clear_password.substr(0, clear_password.length-3);
debug("Clear password " + clear_password);
field.value = clear_password;
}
else
{
debug("No password found");
ret = SERVER.FAILED;
notifications.notify({
title: "gPasss",
text: "No password found in database",
data: "No password found in database",
});
}
}, false);
gPassRequest.addEventListener("error", function(evt) {
debug("error");
ret = false;
notifications.notify({
title: "gPasss",
text: "Error",
data: "Error",
});
}, false);
debug("connect to " + prefSet.prefs.account_url);
gPassRequest.open("POST", prefSet.prefs.account_url, false);
gPassRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
gPassRequest.send(keys);
return ret;
}
function wildcard_domain(domain)
{
parts = domain.split(".");
@ -78,17 +257,19 @@ function on_sumbit(e)
{
var form = this;
var fields = form.getElementsByTagName("input");
var my_map = new Hashtable();
domain = parseURI.parseUri(form.ownerDocument.baseURI);
domain = domain["host"];
wdomain = wildcard_domain(domain);
salt = parseURI.parseUri(prefSet.prefs["account_url"]);
salt = parseURI.parseUri(prefSet.prefs.account_url);
salt = salt["host"] + salt["path"];
debug("salt " + salt);
user = null;
all_logins = new Array;
// Get all <input type="text"> && <input type="email">
for (i=0; i<fields.length; i++)
{
@ -96,10 +277,23 @@ function on_sumbit(e)
if (field.getAttribute("type") == "text" || field.getAttribute("type") == "email")
{
if (field.hasAttribute("name") && field.value != "")
my_map.put(field.getAttribute("name"), field.value);
{
name = field.getAttribute("name");
// Subset of common user field
if (name == "user") user = field.value;
else if (name == "usr") user = field.value;
else if (name == "username") user = field.value;
else if (name == "login") user = field.value;
all_logins.push(field.value);
}
}
}
if (user != null)
logins = new Array(user);
else
logins = all_logins;
// Look for <input type="password" value="@@...">
for (i=0; i<fields.length; i++)
{
@ -113,128 +307,30 @@ function on_sumbit(e)
continue;
mkey = password.substring(2);
mkey = pkdbf2.pkdbf2(mkey, salt, 1000, 256/8);
user = null;
// Subset of common user field
if (my_map.containsKey("user")) user = my_map.get("user");
else if (my_map.containsKey("usr")) user = my_map.get("usr");
else if (my_map.containsKey("username")) user = my_map.get("username");
else if (my_map.containsKey("login")) user = my_map.get("login");
var ret = ask_server(field, logins, domain, wdomain, mkey, salt);
// If no one found, use all
logins = (user != null) ? new Array(user) : my_map.values();
keys = "";
for(a=0, b=logins.length; a<logins.length; a++)
switch(ret)
{
v = "@@" + domain + ";" + logins[a];
debug("will encrypt " + v);
debug("with " + a2hex(mkey));
enc = aes.encryptLongString(v, aes.init(mkey));
aes.finish();
debug("res " + enc);
keys += (keys.length != 0) ? "&" : "";
keys += "k" + a + "=" + a2hex(enc);
if (wdomain != "")
case SERVER.OK:
return true;
case SERVER.FAILED:
if (logins !== all_logins)
{
v = "@@" + wdomain + ";" + logins[a];
debug("will encrypt " + v);
debug("with " + a2hex(mkey));
enc = aes.encryptLongString(v, aes.init(mkey));
aes.finish();
debug("res " + enc);
keys += (keys.length != 0) ? "&" : "";
keys += "k" + (b++) + "=" + a2hex(enc);
ret = ask_server(field, all_logins, domain, wdomain, mkey, salt);
if (ret == SERVER.OK)
return true;
}
}
debug(keys);
// Need to do a synchronous request
var gPassRequest = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
createInstance(Ci.nsIXMLHttpRequest);
var ret = true;
// gPassRequest.addEventListener("progress", function(evt) { ; }, false);
gPassRequest.addEventListener("load", function(evt) {
r = this.responseText.split("\n");
debug("resp " + r);
protocol = r[0].split("=");
if ((protocol.length == 2 && protocol[1] != "gpass-2") || protocol.length != 2)
{
ret = false;
if (protocol.length == 2 && protocol[1].startsWith("gpass"))
{
notifications.notify({
title: "gPasss",
text: "Protocol version not supported, please upgrade your addon",
data: "Protocol version not supported, please upgrade your addon",
});
}
else
{
notifications.notify({
title: "gPasss",
text: "Error : It seems that it's not a gPass server",
data: this.responseText,
});
}
}
else
{
if (r[1] != "<end>" && r[1].startsWith("pass="))
{
ciphered_password = r[1].split("=");
ciphered_password = ciphered_password[1];
debug("Ciphered password : " + ciphered_password);
clear_password = aes.decryptLongString(hex2a(ciphered_password), aes.init(mkey));
aes.finish();
// Remove salt
clear_password = clear_password.replace(/\0*$/, "");
clear_password = clear_password.substr(0, clear_password.length-3);
debug("Clear password " + clear_password);
field.value = clear_password;
}
else
{
debug("No password found");
ret = false;
notifications.notify({
title: "gPasss",
text: "No password found in database",
data: "No password found in database",
});
}
}
}, false);
gPassRequest.addEventListener("error", function(evt) {
debug("error");
ret = false;
notifications.notify({
title: "gPasss",
text: "Error",
data: "Error",
});
}, false);
debug("connect to " + prefSet.prefs["account_url"]);
gPassRequest.open("POST", prefSet.prefs["account_url"], false);
gPassRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
gPassRequest.send(keys);
if (!ret)
{
e.preventDefault();
return ret;
break;
case SERVER.RESTART_REQUEST:
i = -1; // Restart loop
break;
}
}
}
return true;
}
function document_loaded(event)
@ -273,12 +369,11 @@ observerService.addObserver(httpRequestObserver, "content-document-global-create
function self_test()
{
if((res = a2hex(hmac256("Jefe", "what do ya want for nothing?"))) !=
"5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843")
console.log("HMAC256 failed " + res);
if((res = a2hex(pkdbf2.pkdbf2("password", "salt", 4096, 256/8))) !=
"c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a")
console.log("PKDBF2 failed " + res);
else
console.log("All is OK ! " + mkey);
}
console.log("All is OK ! ");
}
// self_test();

View File

@ -5,12 +5,22 @@
"description": "gPass : global password manager",
"author": "Grégory Soutadé",
"license": "GNU GPL v3",
"version": "0.2",
"preferences": [{
"version": "0.3",
"preferences": [
{
"name": "account_url",
"title": "Account URL",
"description": "URL of your gPass account",
"type": "string",
"value": "http://gpass-demo.soutade.fr/demo"
}]
},
{
"name": "pkdbf2_level",
"title": "PKDBF2 Level",
"description": "Number of iterations used to derivate master key",
"type": "integer",
"value": 1000,
"hidden" : true
}
]
}

View File

@ -1,6 +1,6 @@
<?php
/*
Copyright (C) 2013 Grégory Soutadé
Copyright (C) 2013-2014 Grégory Soutadé
This file is part of gPass.
@ -18,6 +18,8 @@
along with gPass. If not, see <http://www.gnu.org/licenses/>.
*/
include("conf.php");
function load_database()
{
try {
@ -31,7 +33,7 @@ function load_database()
return $db;
}
$PROTOCOL_VERSION = 2;
$PROTOCOL_VERSION = 3;
$db = load_database();
@ -40,6 +42,8 @@ $res = "";
$statement = $db->prepare("SELECT password FROM gpass WHERE login=:login");
echo "protocol=gpass-$PROTOCOL_VERSION\n";
if ($PKDBF2_LEVEL != 1000)
echo "pkdbf2_level=$PKDBF2_LEVEL\n";
for ($i=0; isset($_POST["k$i"]); $i++)
{

41
server/conf.php Normal file
View File

@ -0,0 +1,41 @@
/*
Copyright (C) 2013-2014 Grégory Soutadé
This file is part of gPass.
gPass is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
gPass 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with gPass. If not, see <http://www.gnu.org/licenses/>.
*/
<?php
/*
User interface display or not ciphered passwords. Set to false avoid database leakage by user interface (but not by raw HTTP request).
*/
$VIEW_CIPHERED_PASSWORDS=true;
/*
Allows user creation
*/
$ADMIN_MODE=true;
/*
Number of iterations for PKDBF2 algorithm.
Minimum recommended level is 1000, but you can increase
this value to have a better security (need more computation
power).
!! Warning !! This impact master keys. So if you change
this value with existings masterkeys, they will unusable !
*/
$PKDBF2_LEVEL=1000;
?>

View File

@ -1,6 +1,6 @@
<?php
/*
Copyright (C) 2013 Grégory Soutadé
Copyright (C) 2013-2014 Grégory Soutadé
This file is part of gPass.
@ -20,10 +20,10 @@
include('functions.php');
include('conf.php');
session_start();
$VIEW_CIPHERED_PASSWORDS=true;
$ADMIN_MODE=true;
$user = "";
if ($ADMIN_MODE && isset($_POST['create_user']))
@ -51,6 +51,11 @@ else
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
<link rel="stylesheet" type="text/css" href="ressources/gpass.css" />
<script language="javascript">
<?php
echo "pkdbf2_level=$PKDBF2_LEVEL;\n";
?>
</script>
<script src="ressources/jsaes.js"></script>
<script src="ressources/jssha256.js"></script>
<script src="ressources/hmac.js"></script>

View File

@ -103,7 +103,7 @@ function a2hex(str) {
function derive_mkey(user, mkey)
{
url = url_domain(document.URL) + "/" + user;
mkey = a2hex(pkdbf2(mkey, url, 1000, 256/8));
mkey = a2hex(pkdbf2(mkey, url, pkdbf2_level, 256/8));
return mkey;
}