I have set up a zencart on my web server and the passwords for admin functions are encrypted (or obfuscated in some way) in the sql database associated with zencart - can anyone tell me the extent of this encryption? I want to know if someone who managed to get hold of the database would be able to get access to the data in the password section of the database or whether I would be protected against that specific part of the data becoming public by its encryption.
If it is encrypted, how is it encrypted and how easily can they decrypt it?
It is a default zen cart set up.
Kind regards
Passwords in ZenCart are stored as MD5 hash created for a salt + password. The format stored in the SQL database is MD5Hash:Salt.
Salting the password before creating the hash prevents users with access to the user table from reverse engeneering the password with help from rainbow tables. So, you are secure, but it could be better if a SHA-2 algorithm was used.
With PHP you can create data for storage with this code:
$hashedPassword = md5($salt.$password).':'.$salt;
Newer versions of ZenCart use the SHA256 hashing algorithm with a random salt (even though for some unknown reason the variable is called "password"):
function zen_encrypt_password_new($plain)
{
$password = '';
for($i = 0; $i < 40; $i ++) {
$password .= zen_rand();
}
$salt = hash('sha256', $password);
$password = hash('sha256', $salt . $plain) . ':' . $salt;
return $password;
}
(from version 1.5.4 - includes/functions/password_funcs.php)
Related
I understand I can encrypt and store the contents of files (csv mainly) using the techniques explained here and here.
However, I am looking for a way to prevent anyone from accessing these files, even users with sudo access to the server. The only one (or group of people) who should be able to access the encrypted files would be those who have a password or encryption key chosen by me. Is this possible?
By default the file will show encrypted data, hence even if the file anyone get cant seen data. however you can also put key protectection using class from Encrypt directly or using spatie crypt
Here LOOK Spatie link
You can also use the default crypt like this
use Illuminate\Encryption\Encrypter;
//Keys and cipher used by encrypter(s)
$fromKey = base64_decode("from_key_as_a_base_64_encoded_string");
$toKey = base64_decode("to_key_as_a_base_64_encoded_string");
$cipher = "AES-256-CBC"; //or AES-128-CBC if you prefer
//Create two encrypters using different keys for each
$encrypterFrom = new Encrypter($fromKey, $cipher);
$encrypterTo = new Encrypter($toKey, $cipher);
//Decrypt a string that was encrypted using the "from" key
$decryptedFromString = $encrypterFrom->decryptString("gobbledygook=that=is=a=from=key=encrypted=string==");
//Now encrypt the decrypted string using the "to" key
$encryptedToString = $encrypterTo->encryptString($decryptedFromString);
From the Mega Security Whitepaper:
The API does not store the unhashed Authentication Key sent by the user. It only stores the Hashed Authentication Key to prevent “pass-the-hash” attacks (wherein the scenario of a leaked database, an attacker would just pass the Hashed Authentication Key to get authenticated and carry out actions as the real user). The server always hashes the Authentication Key received from the client which prevents this attack vector.
If the Hashed Authentication Key does not match the result in the database, the API responds with a negative response to indicate failure. The API side avoids timing attacks here by using Double HMAC Verification (https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/).
Unfortunately the link is dead and a google search often points back to the same source. Can you please explain how double hmac verification works when you need the original key to verify the signature? Thank you
That URL has been archived by Wayback Machine so you can still read the full blog post: http://web.archive.org/web/20160203044316/https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/
The post contains a C# code snippet that demonstrates how to perform a double HMAC verification. In the end is pretty easy, just as the name "double HMAC" indicates it performs an HMAC two times, using the same HMAC-key.
public void validateHMACSHA256(byte[]receivedHMAC, byte[]message, byte[]key) {
HashAlgorithm hashAlgorithm = new HMACSHA256(key);
// size and algorithm choice are not secret; no weakness in failing fast here.
if (receivedHMAC.Length != hashAlgorithm.HashSize / 8) {
Throw new CryptographicException("HMAC verification failure.");
}
byte[]calculatedHMAC = hashAlgorithm.ComputeHash(message);
// Now we HMAC both values again before comparing to randomize byte order.
// These two lines are all that is required to prevent many existing implementations
// vulnerable to adaptive chosen ciphertext attacks using the timing side channel.
receivedHMAC = hashAlgorithm.ComputeHash(receivedHMAC);
calculatedHMAC = hashAlgorithm.ComputeHash(calculatedHMAC);
for (int i = 0; i < calculatedHMAC.Length; i++) {
if (receivedHMAC[i] != calculatedHMAC[i]) {
throw new CryptographicException("HMAC verification failure.");
}
}
}
There's plenty of discussion on the best algorithm - but what if you're already in production? How do you upgrade without having to reset on the user?
EDIT/DISCLAIMER: Although I originally wanted a "quick fix" solution and chose orip's response, I must concede that if security in your application is important enough to be even bothering with this issue, then a quick fix is the wrong mentality and his proposed solution is probably inadequate.
One option is to make your stored hash include an algorithm version number - so you start with algorithm 0 (e.g. MD5) and store
0:ab0123fe
then when you upgrade to SHA-1, you bump the version number to 1:
1:babababa192df1312
(no, I know these lengths probably aren't right).
That way you can always tell which version to check against when validating a password. You can invalidate old algorithms just by wiping stored hashes which start with that version number.
If you've already got hashes in production without a version number, just choose a scheme such that you can easily recognise unversioned hashes - for example, using the above scheme of a colon, any hash which doesn't contain a colon must by definition predate the versioning scheme, so can be inferred to be version 0 (or whatever).
A cool way to secure all the existing passwords: use the existing hash as the input for the new, and better, password hash.
So if your existing hashes are straight MD5s, and you plan on moving to some form of PBKDF2 (or bcrypt, or scrypt), then change your password hash to:
PBKDF2( MD5( password ) )
You already have the MD5 in your database so all you do is apply PBKDF2 to it.
The reason this works well is that the weaknesses of MD5 vs other hashes (e.g. SHA-*) don't affect password use. For example, its collision vulnerabilities are devastating for digital signatures but they don't affect password hashes. Compared to longer hashes MD5 reduces the hash search-space somewhat with its 128-bit output, but this is insignificant compared to the password search space itself which is much much smaller.
What makes a password hash strong is slowing down (achieved in PBKDF2 by iterations) and a random, long-enough salt - the initial MD5 doesn't adversely affect either of them.
And while you're at it, add a version field to the passwords too.
EDIT: The cryptography StackExchange has an interesting discussion on this method.
Wait until your user logs in (so you have the password in plaintext), then hash it with the new algorithm & save it in your database.
One way to do it is to:
Introduce new field for new password
When the user logs in check the password against the old hash
If OK, hash the clear text password with the new hash
Remove the old hash
Then gradually you will have only passwords with the new hash
You probably can't change the password hashing scheme now, unless you're storing passwords in plain text. What you can do is re-hash the member passwords using a better hashing scheme after each user has successfully logged in.
You can try this:
First add a new column to your members table, or which ever table stores passwords.
ALTER TABLE members ADD is_pass_upgraded tinyint(1) default 0;
Next, in your code that authenticates users, add some additional logic (I'm using PHP):
<?php
$username = $_POST['username'];
$password = $_POST['password'];
$auth_success = authenticateUser($username, $password);
if (!$auth_success) {
/**
* They entered the wrong username/password. Redirect them back
* to the login page.
*/
} else {
/**
* Check to see if the member's password has been upgraded yet
*/
$username = mysql_real_escape_string($username);
$sql = "SELECT id FROM members WHERE username = '$username' AND is_pass_upgraded = 0 LIMIT 1";
$results = mysql_query($sql);
/**
* Getting any results from the query means their password hasn't been
* upgraded yet. We will upgrade it now.
*/
if (mysql_num_rows($results) > 0) {
/**
* Generate a new password hash using your new algorithm. That's
* what the generateNewPasswordHash() function does.
*/
$password = generateNewPasswordHash($password);
$password = mysql_real_escape_string($password);
/**
* Now that we have a new password hash, we'll update the member table
* with the new password hash, and change the is_pass_upgraded flag.
*/
$sql = "UPDATE members SET password = '$password', is_pass_upgraded = 1 WHERE username = '$username' LIMIT 1";
mysql_query($sql);
}
}
Your authenticateUser() function would need to be changed to something similar to this:
<?php
function authenticateUser($username, $password)
{
$username = mysql_real_escape_string($username);
/**
* We need password hashes using your old system (md5 for example)
* and your new system.
*/
$old_password_hashed = md5($password);
$new_password_hashed = generateBetterPasswordHash($password);
$old_password_hashed = mysql_real_escape_string($old_password_hashed);
$new_password_hashed = mysql_real_escape_string($new_password_hashed);
$sql = "SELECT *
FROM members
WHERE username = '$username'
AND
(
(is_pass_upgraded = 0 AND password = '$old_password_hashed')
OR
(is_pass_upgraded = 1 AND password = '$new_password_hashed')
)
LIMIT 1";
$results = mysql_query($sql);
if (mysql_num_rows($results) > 0) {
$row = mysql_fetch_assoc($results);
startUserSession($row);
return true;
} else {
return false;
}
}
There's upsides and downsides to this approach. On the upsides, an individual member's password becomes more secure after they've logged in. The downside is everyone's passwords aren't secured.
I'd only do this for maybe 2 weeks. I'd send an email to all my members, and tell them they have 2 weeks to log into their account because of site upgrades. If they fail to log in within 2 weeks they'll need to use the password recovery system to reset their password.
Just re-hash the plain text when they authenticate the next time. Oah and use SHA-256 with a salt of base256 (full byte) and 256 bytes in size.
A web application contains sensitive data of the users. Neither the operator of the web application nor the hosting provider should be able to see this data. Therefore I wanted to store these data in the DB encrypted with the entrance password of the users.
dataInDB = encrypt (rawData, user password)
With this strategy it is however not possible to implement the usual use case for password recovery: Since usually only the hash value of the password is stored by the web app, the application cannot send the old, forgotten password to the user. And with the assignment of a new, coincidental password the encrypted data in the DB are no longer readable.
Is there any other solution ?
A possible solution (I am not responsible for any destruction):
When encrypting sensitive data, don't use the user's password as the key. Rather, derive the key from the user's password (preferably using a standard algorithm such as PBKDF2). Just in case the user forgets their password, you can keep a copy of this derived key (encrypted using a different key derived from the user's answer). If the user forgets their password, they can answer their security question. Only the correct answer will decrypt the original password key (not the original password). This affords you the opportunity to re-encrypt the sensitive information.
I will demonstrate using (Python-esque) pseudo code, but first let's look at a possible table for the users. Don't get caught up in the columns just yet, they will become clear soon...
CREATE TABLE USERS
(
user_name VARCHAR,
-- ... lots of other, useful columns ...
password_key_iterations NUMBER,
password_key_salt BINARY,
password_key_iv BINARY,
encrypted_password_key BINARY,
question VARCHAR,
answer_key_iterations NUMBER,
answer_key_salt BINARY
)
When it comes time to register a user, they must provide a question and answer:
def register_user(user_name, password, question, answer):
user = User()
# The question is simply stored for later use
user.question = question
# The password secret key is derived from the user's password
user.password_key_iterations = generate_random_number(from=1000, to=2000)
user.password_key_salt = generate_random_salt()
password_key = derive_key(password, iterations=user.password_key_iterations, salt=user.password_key_salt)
# The answer secret key is derived from the answer to the user's security question
user.answer_key_iterations = generate_random_number(from=1000, to=2000)
user.answer_key_salt = generate_random_salt()
answer_key = derive_key(answer, iterations=user.answer_key_iterations, salt=user.answer_key_salt)
# The password secret key is encrypted using the key derived from the answer
user.password_key_iv = generate_random_iv()
user.encrypted_password_key = encrypt(password_key, key=answer_key, iv=user.password_key_iv)
database.insert_user(user)
Should the user forget their password, the system will still have to ask the user to answer their security question. Their password cannot be recovered, but the key derived from the password can be. This allows the system to re-encrypt the sensitive information using the new password:
def reset_password(user_name, answer, new_password):
user = database.rerieve_user(user_name)
answer_key = derive_key(answer, iterations=user.answer_key_iterations, salt=user.answer_key_salt)
# The answer key decrypts the old password key
old_password_key = decrypt(user.encrypted_password_key, key=answer_key, iv=user.password_key_iv)
# TODO: Decrypt sensitive data using the old password key
new_password_key = derive_key(new_password, iterations=user.password_key_iterations, salt=user.password_key_salt)
# TODO: Re-encrypt sensitive data using the new password key
user.encrypted_password_key = encrypt(new_password_key, key=user.answer_key, iv=user.password_key_iv)
database.update_user(user)
Of course, there are some general cryptographic principles not explicitly highlighted here (cipher modes, etc...) that are the responsibility of the implementer to familiarize themselves with.
Hope this helps a little! :)
Update courtesy of Eadwacer's comment
As Eadwacer commented:
I would avoid deriving the key directly from the password (limited entropy and changing the password will require re-encrypting all of the data). Instead, create a random key for each user and use the password to encrypt the key. You would also encrypt the key using a key derived from the security questions.
Here is a modified version of my solution taking his excellent advice into consideration:
CREATE TABLE USERS
(
user_name VARCHAR,
-- ... lots of other, useful columns ...
password_key_iterations NUMBER,
password_key_salt BINARY,
password_encrypted_data_key BINARY,
password_encrypted_data_key_iv BINARY,
question VARCHAR,
answer_key_iterations NUMBER,
answer_key_salt BINARY,
answer_encrypted_data_key BINARY,
answer_encrypted_data_key_iv BINARY,
)
You would then register the user as follows:
def register_user(user_name, password, question, answer):
user = User()
# The question is simply stored for later use
user.question = question
# The randomly-generated data key will ultimately encrypt our sensitive data
data_key = generate_random_key()
# The password key is derived from the password
user.password_key_iterations = generate_random_number(from=1000, to=2000)
user.password_key_salt = generate_random_salt()
password_key = derive_key(password, iterations=user.password_key_iterations, salt=user.password_key_salt)
# The answer key is derived from the answer
user.answer_key_iterations = generate_random_number(from=1000, to=2000)
user.answer_key_salt = generate_random_salt()
answer_key = derive_key(answer, iterations=user.answer_key_iterations, salt=user.answer_key_salt)
# The data key is encrypted using the password key
user.password_encrypted_data_key_iv = generate_random_iv()
user.password_encrypted_data_key = encrypt(data_key, key=password_key, iv=user.password_encrypted_data_key_iv)
# The data key is encrypted using the answer key
user.answer_encrypted_data_key_iv = generate_random_iv()
user.answer_encrypted_data_key = encrypt(data_key, key=answer_key, iv=user.answer_encrypted_data_key_iv)
database.insert_user(user)
Now, resetting a user's password looks like this:
def reset_password(user_name, answer, new_password):
user = database.rerieve_user(user_name)
answer_key = derive_key(answer, iterations=user.answer_key_iterations, salt=user.answer_key_salt)
# The answer key decrypts the data key
data_key = decrypt(user.answer_encrypted_data_key, key=answer_key, iv=user.answer_encrypted_data_key_iv)
# Instead of re-encrypting all the sensitive data, we simply re-encrypt the password key
new_password_key = derive_key(new_password, iterations=user.password_key_iterations, salt=user.password_key_salt)
user.password_encrypted_data_key = encrypt(data_key, key=new_password_key, iv=user.password_encrypted_data_key_iv)
database.update_user(user)
Hopefully my head is still functioning clearly tonight...
I have some down time and I am think of picking a new project for fun. I am a college student and every year we have a online pitch competition. I want to create a project for this pitch competition that is approx 9 months from now. The problem is the project requires very high security and the competition is a very competitive.
Things I need to be able to do:
1. Store HIPAA or ePHI (.pdf|.gif|.jpg|.doc)
2. Strong access control
3. Support large number of users and files (1 million +)
4. Full Audit Reports (oh ePhi you are such a pain)
5. Encryption
Proposed Solutions
0) Place the web app on a secure dedicated sever behind a firewall
1) Store files in a file say “secure_files/” and then use mod_rewrite to restrict access to this directory.
Something on the lines of:
#Removes access to the secure_files folder by users.
RewriteCond %{REQUEST_URI} ^secure_files.*
RewriteRule ^(.*)$ /index.php?/$1 [L]
Then use a php script to open the files if the user has privileges to do so. So Could I just use:
------
-SQL
------
------
- create files table
-----
CREATE TABLE `files` (
id INT NOT NULL AUTO_INCREMENT,
file_name VARCHAR(50) NOT NULL,
PRIMARY KEY('id')
);
------
- create files table
-----
CREATE TABLE `privileges` (
uesr_id INT NOT NULL,
file_id INT NOT NULL,
);
------
- create users table
-----
CREATE TABLE `users` (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(20) NOT NULL,
email VARCHAR(50) NOT NULL,
password CHAR(40) NOT NULL,
PRIMARY KEY('id')
);
<?php
public function get_user_files($filename)
{
//this is set during login
$user_id = $this->session->userdata('user_id');
//check to see if the user has privileges to access the file and gets the file name
$query = $this->db->join('privileges','privileges.id = files.id')
->select('files.file_name')
->where('privileges.user_id',$user_id)
->where('files.file_name',$file_name)
->limit(1)
->get('files');
$file = $query->row()->files.file_name;
if($file)
{
//user has privileges to access the file so include it
$handle = fopen($file, "rb");
$data['file'] = fread($handle, filesize($file));
fclose($handle);
}
$this->load->view('files',$data);
}
?>
2) Use CI sessions class to add a “user” to the session.
The controller checks to see if the session is set:
<?php
public function __construct()
{
parent::__construct();
if($this->secure(array('userType' => 'user')) == FALSE)
{
$this->session->set_flashdata('flashError', 'You must be logged into a valid user account to access this section.');
$this->session->sess_destroy();
redirect('login');
}
}
function secure($options = array())
{
$userType = $this->session->userdata('userType');
if(is_array($options['userType']))
{
foreach($options['userType'] as $optionUserType)
{
if($optionUserType == $userType) return true;
}
}
else
{
if($userType == $options['userType']) return true;
}
return false;
}
?>
3) Rotate round robin between multiple web severs. I have never done this so I have no idea how to do this. I have no idea how to deal with the multiple database servers. Any ideas/suggestions?
4) Use Oracle Enterprise Standard Database Auditing. I wish I could use MySQL, but I cannot find any auditing support. I could use MySQL and use PITA. Has anyone used point-in-time architecture (PITA) with MySQL? can you share your experience?
5) So obviously I can hash the passwords with a one way salted hash. But do I need to encrypt everything? Also I don’t see how AES_ENCRYPT(str,key_str) improves secuirty at all. I guess it could prevent an admin from looking at a database? Can/should I encrypt everything in the “secure_files/” folder? Could I just use full disk encryption like BitLocker?
Basically can I achieve online bank level security with php and CI? Can you make any other suggestions besides the worthless “your an idiot go pay an expert because you know nothing” suggestions?
Thank you for taking the time to read through this.
adopted from Redux Auth
With regards to one-way hash. My mistake for saying encryption. I usually do the do something similar to:
salt_length = '9';
}
public function hash($password = false)
{
$salt_length = $this->salt_length;
if ($password === false)
{
return false;
}
$salt = $this->salt();
$password = $salt . substr(hash('sha256',$salt . $password), 0, -$salt_length);
return $password;
}
private function salt()
{
return substr(md5(uniqid(rand(), true)), 0, $this->salt_length);
}
}
?>
Edit:
Encrypting sensitive data in a sql database defends against 3 major threats.
Internal Threats:
Sys admin and developers.
SQL Injection:
If your database is configured properly sql injection should only provide the attacker with access to the application's database, and nothing else. In mysql make sure you revoke FILE privileged as this could be used to read a hard-coded key or configuration file.
Even More Secure Backups:
Security in layers.
So obviously I can encrypt the
passwords with a one way salted hash.
Encryption is not the same as hashing. Encryption implies that there is a means of decrypting the data. Using an encryption function for passwords is a vulnerablity recognized by CWE-257. Passwords must always use a salted hash, and sha-256 is a great algorithm. The salt should be a Cryptographic nonce, as in a very random value that is only used 1 per hash.
MySQL's AES_ENCRYPT() sucks, its using ECB mode which is terrible. If the function call doesn't have an IV its probably ECB mode, if the IV is null then its a violation of CWE-329.
Plain text:
encrypted with ECB mode:
Encryption is difficult, you have to worry about Initialization vectors, modes of operations, key storage, and string2key functions. The vast majority of programmers think cryptography is easy, but they manage seriously mess things up. Get a copy of Practical Cryptography, its straight to the point and not math heavy. If you enjoy math then go with "The Handbook".
EDIT:
I don't like your nonce generation very much because it has a bad entropy/size ratio. A base16 salt is a waste when you could have base 256 salt. Keep in mind that most (probably all) message digest implementations are binary safe. Also uniqid() uses a lot of time in its calculation, and if it only used time it would be a violation of CWE-337. Now on the other hand mt_rand() kicks ass. Also keep in mind you should probably store this as a base64 and then base64 decode it before use in your hash function.
public function nonce($size=32){//256 bit == 32byte.
for($x=0;$x<$size;$x++){
$ret.=chr(mt_rand(0,255));
}
return base64_encode($ret);
}