MySQL encrypt() with salt accepts random characters after password - mysql

I have a table with usernames and encrypted passwords.
The passwords are encrypted by means of MySQL encrypt() together with a salt (the first two characters of the password).
Recently I've noticed that MySQL accepts passwords even if they contain random characters at the end.
Suppose we have this kind of table:
SET NAMES utf8;
SET foreign_key_checks = 0;
SET time_zone = 'SYSTEM';
SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO';
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `user` (`id`, `username`, `password`) VALUES
(11, 'ricardomontalban', ENCRYPT(11111111,11));
And now I query for my user:
-- The following shows the appropriate response --
SELECT * FROM user WHERE username = "ricardomontalban" AND password = ENCRYPT(11111111,11);
-- HOWEVER, the following query also shows a result, even with random characters appended!!! --
SELECT * FROM user WHERE username = "ricardomontalban" AND password = ENCRYPT("11111111-55669964s5465sqsfqsdf",11);
-- No problem with prepended random characters though --
SELECT * FROM user WHERE username = "ricardomontalban" AND password = ENCRYPT("smlkfjmlsdkfjslqf-11111111",11);
I've created an SQL Fiddle to show this example in real time:
http://sqlfiddle.com/#!2/898d5/9
What am I doing wrong? Should I even be using this encryption method?
Any suggestions are greatly appreciated.

As per the documentation:
ENCRYPT() ignores all but the first eight characters of str, at least on some systems. This behavior is determined by the implementation of the underlying crypt() system call.

To answer your other questions:
Using the first two (or any) characters from the password as the salt is also wrong. The idea of the salt is to provide some randomness to the encrypted passwords so that users who have the same password will have different hashes.
Use an established encryption library (such as bcrypt) for whatever programming language you are using.

Related

Mysql General Log Retrieve BLOB Content

Mysql :5.7
General Log: 1
log_output : Table
I have written some code using java to store images in a blob column of a table (tbl_attachment_mst).
My General Log settings are turned on and is configured to write to 'table'.
Whenever i add an image to tbl_attachment_mst , mysql does log it in the mysql.general_log table with _binary('some unreadable characters not sure what this is').
I have accidently lost contents of the table tbl_attachment_mst . Is it possible to recover my data from the mysql.generaL_log table??
I think i am having some issue with the character set while trying to execute the query that is stored in mysql.general_log.
(From comment)
CREATE TABLE tbl_attachment_mst (
attachment_id int(11) NOT NULL AUTO_INCREMENT,
file_name varchar(200) DEFAULT NULL,
created_date datetime DEFAULT NULL,
activate_flag tinyint(4) DEFAULT NULL,
file_id int(11) DEFAULT NULL,
type varchar(300) DEFAULT NULL,
attachment_asblob longblob,
PRIMARY KEY (attachment_id)
) ENGINE=MyISAM DEFAULT CHARSET=latin1
insert into tbl_attachment_mst values
(default,'test.jpg',sysdate(),1,1,'Type',
_binary('some characters here'))
Grab it fast. I suspect the general_log, as a table, gets flushed in some fashion pretty fast.
It works just like a table, so SELECTs work. However, your SELECTs go into the general log unless you have turned it off.
Recommend doing SELECT ... HEX(col) ... to avoid the unprintable characters.
Please provide more details so I can try to simulate it and see what would work best.
SHOW CREATE TABLE
An approximation of the INSERT statement (or whatever was involved)
And if the grabbed hex is useful, you can use something like this to reverse the steps:
INSERT ... VALUES (... UNHEX(hex_string) ... )
using the below set of queries, i was able to get the exact data that was inserted into the db at that particular time.....
SELECT argument INTO #sql FROM mysql.general_log limit 1;
PREPARE sql_query FROM #sql;
EXECUTE sql_query;
These queries would insert the data back into the tbl_attachment_mst...

Why won't my SQL passwordhashing-procedure run?

I am trying to create a SQL procedure that hashes password inputs. This code won't run and I am not getting any useful response errors.
The first part creates the table, the second creates the procedure. When I call on my procedure in the third part it send the values into the procedure. There the password is supposed to be hashed using SHA2_512 and inserted into the table we made eralier.
I used online research to make this code, the parts I don't get is:
The N before my values
The SetNoCount
The #responsemessage
-- makes Admin table
CREATE TABLE IF NOT EXISTS `AdminUser` (
`AdminID` smallint(6) NOT NULL AUTO_INCREMENT,
`Username` char(15) NOT NULL,
`PasswordHash` BINARY(64) NOT NULL,
`Fornavn` char(30) NOT NULL,
`Etternavn` char(40) NOT NULL,
`Email` char(40) NOT NULL,
PRIMARY KEY (`AdminID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- Makes hashing procedure
CREATE OR REPLACE PROCEDURE vm_ski.addAdmin
#pUsername NVARCHAR(50),
#pPassword NVARCHAR(50),
#pFornavn NVARCHAR(30),
#pEtternavn NVARCHAR(40),
#pEmail NVARCHAR(40),
#responseMessage NVARCHAR(250)='' OUTPUT
AS
BEGIN
SET NOCOUNT ON
BEGIN TRY
INSERT INTO vm_ski.AdminUser (Username, PasswordHash, Fornavn, Etternavn, Email)
VALUES(#pUsername, HASHBYTES('SHA2_512', #pPassword), #pFornavn, #pEtternavn, #pEmail)
SET #responseMessage='Success'
END TRY
BEGIN CATCH
SET #responseMessage=ERROR_MESSAGE()
END CATCH
END;
-- Admin example
DECLARE #responseMessage NVARCHAR(250)
EXECUTE vm_ski.addAdmin
#pUsername = N'sondre',
#pPassword = N'example'
#pFornavn = N'Sondre'
#pEtternavn = N'Morgendal'
#pEmail = N'sondre.example#gmail.com'
;
This is not a direct answer to the question; this is a security note on the methodology of the question
Do NOT hash passwords in MySQL. The data given to MySQL is plaintext, and easily intercepted by MySQL processing logs as well as possibly numerous other places before being dumped in the database (such as if message packets sent to the database are non-localhost and are non-TLS). ( Why? )
When hashing passwords you want to be doing so as early in the process as possible. This typically means using PHP password_hash and simply dumping only the hashed data in the MySQL.
If you do not use PHP to interact with your SQL then you can use other server methods such as Argon2 or Libsodium.
Also as a side point you should be using the mb4 UTF-8 charset and collations - principly utf8mb4_general_ci ( Why? )

MySQL Case Sensitivity (or otherwise, how to store passwords correctly in MySQL)

CAUSE:
I have a table and the columns are all suitably Collated as utf8mb4_unicode_ci,
CREATE TABLE IF NOT EXISTS `users` (
`user_id` int(8) NOT NULL AUTO_INCREMENT,
`username` varchar(100) NOT NULL,
`pass_word` varchar(512) NOT NULL ,
...etc etc...
PRIMARY KEY (`user_id`),
UNIQUE KEY `email_addr` (`email_addr`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=989 ;
...Including the column storing the password hash (generated from password_hash) such as $2y$14$tFpExwd2TXm43Bd20P4nkMbL1XKxwF.VCpL.FXeVRaUO3FFxGJ4Di.
BUT, I find that due to the case insensitivity of the column, that a hash of $2y$14$tFpExwd2tXm43Bd20P4NKmbL1XKxwF.VCpL.FxEVRaUO3FFxGJ4DI would still allow access.
This means that there are potentially hundreds of collisions possible by storing the data in a case insensitive manner. Not good.
ISSUE:
Now, Is there a way of forcing MySQL to treat pass_word column as a case sensitive column, when doing comparisons. I want to avoid having to edit every occurance of the PHP/SQL querying, and instead simply set the database table column to compare in a case sensitive manner by default.
The utf8mb4 character set does not give me any _cs options, and the only non-_ci option appears to be utf8mb4_bin.
So simple questions:
Does the UTF8mb4_bin character set & collation on MySQL treat standard comparisons case sensitively? [yes]
Dose the UTF8mb4_bin suit what I want to do. Should I use another set, and if so, why?
Are there any issues in storing password_hash outputs in a MySQL utf8mb4_bin column?
Does this approach conveniently sidestep the need to edit the query SQL of each login query? Can I change the column type and then move on?
EDIT
As detailed by nj_ , this is a silly issue that is not an issue at all because the value of pass_word is never directly edited when logging in.
... It's been a long day.
If you're really that worried about the potential 2^55 collisions in your 62^55 address space, you can simply change the column type to BLOB, which is always case-sensitive.
CREATE TABLE IF NOT EXISTS `users` (
`user_id` int(8) NOT NULL AUTO_INCREMENT,
`username` varchar(100) NOT NULL,
`pass_word` BLOB NOT NULL ,
...etc etc...
PRIMARY KEY (`user_id`),
UNIQUE KEY `email_addr` (`email_addr`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=989 ;
Example:
INSERT INTO `users` (..., `pass_word`) VALUES (..., 'AbC');
SELECT * FROM `users` WHERE `pass_word` = 'AbC' LIMIT 0,1000; -> 1 hit
SELECT * FROM `users` WHERE `pass_word` = 'abc' LIMIT 0,1000; -> 0 hits
Case sensitivity is no problem in this case, because you cannot verify the password directly with SQL anyway. A correctly salted password hash cannot be searched for in the database. Search by username only and extract the stored hash from the database:
$sql= 'SELECT * FROM users WHERE username = ?';
$db->prepare($sql);
$db->bind_param('s', $_POST['username']);
Afterwards you can extract the hash from the row and check the entered password against the found hash with the password_verify() function:
// Check if the hash of the entered login password, matches the stored hash.
// The salt and the cost factor will be extracted from $existingHashFromDb.
$isPasswordCorrect = password_verify($password, $existingHashFromDb);

MySQL SHA1 hash does not match

I have a weird problem with a MySQL users table. I have quickly created a simplified version as a testcase.
I have the following table
CREATE TABLE IF NOT EXISTS `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`identity` varchar(255) NOT NULL,
`credential` varchar(255) NOT NULL,
`credentialSalt` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=ucs2 AUTO_INCREMENT=2 ;
INSERT INTO `users` (`id`, `identity`, `credential`, `credentialSalt`) VALUES
(1, 'test', '7288edd0fc3ffcbe93a0cf06e3568e28521687bc', '123');
And I run the following query
SELECT id,
IF (credential = SHA1(CONCAT('test', credentialSalt)), 1, 0) AS dynamicSaltMatches,
credentialSalt AS dynamicSalt,
SHA1(CONCAT('test', credentialSalt)) AS dynamicSaltHash,
IF (credential = SHA1(CONCAT('test', 123)), 1, 0) AS staticSaltMatches,
123 AS staticSalt,
SHA1(CONCAT('test', 123)) AS staticSaltHash
FROM users
WHERE identity = 'test'
Which gives me the following result
The dynamic salt does NOT match while the static salt DOES match.
This is blowing my mind. Can someone help me point out the cause of this?
My MySQL version is 5.5.29
It's because of the default character set of your table. You appear to be running this on a UTF8 database and something in SHA1() is having problems with the differing character sets.
If you change your table declaration to the following it will match again:
CREATE TABLE IF NOT EXISTS `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`identity` varchar(255) NOT NULL,
`credential` varchar(255) NOT NULL,
`credentialSalt` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;
SQL Fiddle
As robertklep commented explicitly casting your string to a character will also work, basically ensure you're using the same characterset when doing comparisons using SHA1()
As the encryption functions documentation says:
Many encryption and compression functions return strings for which the result might contain arbitrary byte values. If you want to store these results, use a column with a VARBINARY or BLOB binary string data type. This will avoid potential problems with trailing space removal or character set conversion that would change data values, such as may occur if you use a nonbinary string data type (CHAR, VARCHAR, TEXT).
This was changed in version 5.5.3:
As of MySQL 5.5.3, the return value is a nonbinary string in the connection character set. Before 5.5.3, the return value is a binary string; see the notes at the beginning of this section about using the value as a nonbinary string.

How do I merge 5 identical MySQL tables?

I want to merge 5 identical-schema (okay, they are not exactly identical but I can edit the field names to make them identical) MySQL databases into one database. Is there any easy way?
CREATE TABLE `users` (
`name` VARCHAR (50) COLLATE utf8_turkish_ci DEFAULT NULL,
`surname` VARCHAR (50) COLLATE utf8_turkish_ci DEFAULT NULL,
`telephone` VARCHAR (50) COLLATE utf8_turkish_ci DEFAULT NULL,
PRIMARY KEY (`id`),
) ENGINE = MyISAM AUTO_INCREMENT = 1000 DEFAULT CHARSET = utf8 COLLATE = utf8_turkish_ci PACK_KEYS = 0 ROW_FORMAT = DYNAMIC
If the tables are exactly the same with column names/types and are named
user1
user2
user3
user4
user5
there are two approaches to handle this:
APPROACH #1 : Load the data into one table
CREATE TABLE user LIKE user1;
INSERT INTO user (name,surname,telephone,...)
SELECT name,surname,telephone,... FROM user1;
INSERT INTO user (name,surname,telephone,...)
SELECT name,surname,telephone,... FROM user2;
INSERT INTO user (name,surname,telephone,...)
SELECT name,surname,telephone,... FROM user3;
INSERT INTO user (name,surname,telephone,...)
SELECT name,surname,telephone,... FROM user4;
INSERT INTO user (name,surname,telephone,...)
SELECT name,surname,telephone,... FROM user5;
If the id is auto_increment all rows get new ids.
APPROACH #2 : Use the MERGE Storage Engine
CREATE TABLE user LIKE user1;
ALTER TABLE user
ENGINE=Mrg_MyISAM
UNION=(user1,user2,user3,user4,user5)
;
Give it a Try !!!
There's no easy way to do this.
You could attempt to alter your tables within the different databases to bring them to be in the most similar format.
Additionally, you could use statements such as
Create table as select
in order to further format the data.
Than you would have to do a MYSQL DUMP of all your databases.
Select only the create statements from the database schema you are interested in following, and add your insert statements (for the data) from all the different databases.
You may also have to perform text manipulation in Excel, or from with mysql in order to get the data in such a format that it is compatible and can be inserted in your final schema.
Any ETL tool, like Clover, would be well suited for your purpose. Just define your column mappings and you should be good to go. Leave a comment if you need further help.