SQL-based authentication

From Rosetta Code
Task
SQL-based authentication
You are encouraged to solve this task according to the task description, using any language you may know.

This task has three parts:

  • Connect to a MySQL database (connect_db)
  • Create user/password records in the following table (create_user)
  • Authenticate login requests against the table (authenticate_user)

This is the table definition:

CREATE TABLE users (
userid INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(32) UNIQUE KEY NOT NULL,
pass_salt tinyblob NOT NULL,
-- a string of 16 random bytes
pass_md5 tinyblob NOT NULL
-- binary MD5 hash of pass_salt concatenated with the password
);

(pass_salt and pass_md5 would be binary(16) values, but MySQL versions before 5.0.15 strip trailing spaces when selecting them.)

C[edit]

Library: mysqlclient
(MySQL client library)
Library: OpenSSL
(for MD5)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <time.h>
 
#include <mysql.h>
#include <openssl/md5.h>
 
void end_with_db(void);
 
MYSQL *mysql = NULL; // global...
 
bool connect_db(const char *host, const char *user, const char *pwd,
const char *db, unsigned int port)
{
if ( mysql == NULL )
{
if (mysql_library_init(0, NULL, NULL)) return false;
mysql = mysql_init(NULL); if ( mysql == NULL ) return false;
MYSQL *myp = mysql_real_connect(mysql, host, user, pwd, db, port, NULL, 0);
if (myp == NULL) {
fprintf(stderr, "connection error: %s\n", mysql_error(mysql));
end_with_db();
return false;
}
}
return true; // already connected... ?
}
 
#define USERNAMELIMIT 32
// no part of the spec, but it is reasonable!!
#define PASSWORDLIMIT 32
#define SALTBYTE 16
bool create_user(const char *username, const char *password)
{
int i;
char binarysalt[SALTBYTE];
char salt[SALTBYTE*2+1];
char md5hash[MD5_DIGEST_LENGTH];
char saltpass[SALTBYTE+PASSWORDLIMIT+1];
char pass_md5[MD5_DIGEST_LENGTH*2 + 1];
char user[USERNAMELIMIT*2 + 1];
char *q = NULL;
static const char query[] =
"INSERT INTO users "
"(username,pass_salt,pass_md5) "
"VALUES ('%s', X'%s', X'%s')";
static const size_t qlen = sizeof query;
 
 
for(i=0; username[i] != '\0' && i < USERNAMELIMIT; i++) ;
if ( username[i] != '\0' ) return false;
for(i=0; password[i] != '\0' && i < PASSWORDLIMIT; i++) ;
if ( password[i] != '\0' ) return false;
 
srand(time(NULL));
 
for(i=0; i < SALTBYTE; i++)
{
// this skews the distribution but it is lazyness-compliant;)
binarysalt[i] = rand()%256;
}
 
(void)mysql_hex_string(salt, binarysalt, SALTBYTE);
 
for(i=0; i < SALTBYTE; i++) saltpass[i] = binarysalt[i];
strcpy(saltpass+SALTBYTE, password);
(void)MD5(saltpass, SALTBYTE + strlen(password), md5hash);
(void)mysql_hex_string(pass_md5, md5hash, MD5_DIGEST_LENGTH);
 
(void)mysql_real_escape_string(mysql, user, username, strlen(username));
 
// salt, pass_md5, user are db-query-ready
q = malloc(qlen + USERNAMELIMIT*2 + MD5_DIGEST_LENGTH*2 + SALTBYTE*2 + 1);
if ( q == NULL ) return false;
sprintf(q, query, user, salt, pass_md5);
#if defined(DEBUG)
fprintf(stderr, "QUERY:\n%s\n\n", q);
#endif
int res = mysql_query(mysql, q);
free(q);
if ( res != 0 )
{
fprintf(stderr, "create_user query error: %s\n", mysql_error(mysql));
return false;
}
return true;
}
 
 
bool authenticate_user(const char *username, const char *password)
{
char user[USERNAMELIMIT*2 + 1];
char md5hash[MD5_DIGEST_LENGTH];
char saltpass[SALTBYTE+PASSWORDLIMIT+1];
bool authok = false;
char *q = NULL;
int i;
static const char query[] =
"SELECT * FROM users WHERE username='%s'";
static const size_t qlen = sizeof query;
 
// can't be authenticated with invalid username or password
for(i=0; username[i] != '\0' && i < USERNAMELIMIT; i++) ;
if ( username[i] != '\0' ) return false;
for(i=0; password[i] != '\0' && i < PASSWORDLIMIT; i++) ;
if ( password[i] != '\0' ) return false;
 
(void)mysql_real_escape_string(mysql, user, username, strlen(username));
 
q = malloc(qlen + strlen(user) + 1);
if (q == NULL) return false;
sprintf(q, query, username);
 
int res = mysql_query(mysql, q);
free(q);
if ( res != 0 )
{
fprintf(stderr, "authenticate_user query error: %s\n", mysql_error(mysql));
return false;
}
 
MYSQL_RES *qr = mysql_store_result(mysql);
if ( qr == NULL ) return false;
 
// should be only a result, or none
if ( mysql_num_rows(qr) != 1 ) {
mysql_free_result(qr);
return false;
}
 
MYSQL_ROW row = mysql_fetch_row(qr); // 1 row must exist
unsigned long *len = mysql_fetch_lengths(qr); // and should have 4 cols...
 
memcpy(saltpass, row[2], len[2]); // len[2] should be SALTBYTE
memcpy(saltpass + len[2], password, strlen(password));
(void)MD5(saltpass, SALTBYTE + strlen(password), md5hash);
 
authok = memcmp(md5hash, row[3], len[3]) == 0;
mysql_free_result(qr);
 
return authok;
}
 
void end_with_db(void)
{
mysql_close(mysql); mysql = NULL;
mysql_library_end();
}
 
int main(int argc, char **argv)
{
 
if ( argc < 4 ) return EXIT_FAILURE;
 
if ( connect_db("localhost", "devel", "", "test", 0 ) )
{
if ( strcmp(argv[1], "add") == 0 )
{
if (create_user(argv[2], argv[3]))
printf("created\n");
} else if ( strcmp(argv[1], "auth") == 0 ) {
if (authenticate_user(argv[2], argv[3]))
printf("authorized\n");
else
printf("access denied\n");
} else {
printf("unknown command\n");
}
end_with_db();
}
return EXIT_SUCCESS;
}

From the command line, program add user password to add users, and program auth user password to see if the user with that password is authorized or not.

Go[edit]

package main
 
import (
"bytes"
"crypto/md5"
"crypto/rand"
"database/sql"
"fmt"
 
_ "github.com/go-sql-driver/mysql"
)
 
func connectDB() (*sql.DB, error) {
return sql.Open("mysql", "rosetta:code@/rc")
}
 
func createUser(db *sql.DB, user, pwd string) error {
salt := make([]byte, 16)
rand.Reader.Read(salt)
_, err := db.Exec(`insert into users (username, pass_salt, pass_md5)
values (?, ?, ?)`
, user, salt, saltHash(salt, pwd))
if err != nil {
return fmt.Errorf("User %s already exits", user)
}
return nil
}
 
func authenticateUser(db *sql.DB, user, pwd string) error {
var salt, hash []byte
row := db.QueryRow(`select pass_salt, pass_md5 from users
where username=?`
, user)
if err := row.Scan(&salt, &hash); err != nil {
return fmt.Errorf("User %s unknown", user)
}
if !bytes.Equal(saltHash(salt, pwd), hash) {
return fmt.Errorf("User %s invalid password", user)
}
return nil
}
 
func saltHash(salt []byte, pwd string) []byte {
h := md5.New()
h.Write(salt)
h.Write([]byte(pwd))
return h.Sum(nil)
}
 
func main() {
// demonstrate
db, err := connectDB()
defer db.Close()
createUser(db, "sam", "123")
err = authenticateUser(db, "sam", "123")
if err == nil {
fmt.Println("User sam authenticated")
}
 
// extra
fmt.Println()
// show contents of database
rows, _ := db.Query(`select username, pass_salt, pass_md5 from users`)
var user string
var salt, hash []byte
for rows.Next() {
rows.Scan(&user, &salt, &hash)
fmt.Printf("%s %x %x\n", user, salt, hash)
}
// try creating same user again
err = createUser(db, "sam", "123")
fmt.Println(err)
// try authenticating unknown user
err = authenticateUser(db, "pam", "123")
fmt.Println(err)
// try wrong password
err = authenticateUser(db, "sam", "1234")
fmt.Println(err)
// clear table to run program again
db.Exec(`truncate table users`)
}
Output:
User sam authenticated

sam d5d1ef775a89f2b504dc808e66087f3d a939f77466e91527e748377cc14dfd66
User sam already exits
User pam unknown
User sam invalid password

Java[edit]

Works with: Java version 1.7
import java.io.UnsupportedEncodingException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.math.BigInteger;
 
 
class UserManager {
private Connection dbConnection;
 
public UserManager() {
}
 
private String md5(String aString) throws NoSuchAlgorithmException, UnsupportedEncodingException {
MessageDigest md;
String hex;
StringBuffer hexString;
byte[] bytesOfMessage;
byte[] theDigest;
 
hexString = new StringBuffer();
bytesOfMessage = aString.getBytes("UTF-8");
md = MessageDigest.getInstance("MD5");
theDigest = md.digest(bytesOfMessage);
 
for (int i = 0; i < theDigest.length; i++) {
hex = Integer.toHexString(0xff & theDigest[i]);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
 
return hexString.toString();
}
 
public void connectDB(String host, int port, String db, String user, String password)
throws ClassNotFoundException, SQLException {
 
Class.forName("com.mysql.jdbc.Driver");
 
this.dbConnection = DriverManager.getConnection("jdbc:mysql://"
+ host
+ ":"
+ port
+ "/"
+ db, user, password);
}
 
public boolean createUser(String user, String password) {
SecureRandom random;
String insert;
String salt;
 
random = new SecureRandom();
salt = new BigInteger(130, random).toString(16);
 
insert = "INSERT INTO users "
+ "(username, pass_salt, pass_md5) "
+ "VALUES (?, ?, ?)";
 
try (PreparedStatement pstmt = this.dbConnection.prepareStatement(insert)) {
pstmt.setString(1, user);
pstmt.setString(2, salt);
pstmt.setString(3, this.md5(salt + password));
pstmt.executeUpdate();
 
return true;
} catch(NoSuchAlgorithmException | SQLException | UnsupportedEncodingException ex) {
return false;
}
}
 
public boolean authenticateUser(String user, String password) {
String pass_md5;
String pass_salt;
String select;
ResultSet res;
 
select = "SELECT pass_salt, pass_md5 FROM users WHERE username = ?";
res = null;
 
try(PreparedStatement pstmt = this.dbConnection.prepareStatement(select)) {
pstmt.setString(1, user);
res = pstmt.executeQuery();
 
res.next(); // We assume that username is unique
 
pass_salt = res.getString(1);
pass_md5 = res.getString(2);
 
if (pass_md5.equals(this.md5(pass_salt + password))) {
return true;
} else {
return false;
}
 
} catch(NoSuchAlgorithmException | SQLException | UnsupportedEncodingException ex) {
return false;
} finally {
try {
if (res instanceof ResultSet && !res.isClosed()) {
res.close();
}
} catch(SQLException ex) {
}
}
}
 
public void closeConnection() {
try {
this.dbConnection.close();
} catch(NullPointerException | SQLException ex) {
}
}
 
public static void main(String[] args) {
UserManager um;
 
um = new UserManager();
try {
um.connectDB("localhost", 3306, "test", "root", "admin");
 
if (um.createUser("johndoe", "test")) {
System.out.println("User created");
}
 
if (um.authenticateUser("johndoe", "test")) {
System.out.println("User authenticated");
}
} catch(ClassNotFoundException | SQLException ex) {
ex.printStackTrace();
} finally {
um.closeConnection();
}
}
}

Mathematica[edit]

Library: DatabaseLink`
Needs["DatabaseLink`"];
connectDb[dbUser_, dbPass_, dbUrl_] :=
OpenSQLConnection[JDBC["mysql", dbUrl], "Username" -> dbUser,
"Password" -> dbPass];
createUser::nameTaken = "The username '`1`' is already taken.";
createUser[dbUser_, dbPass_, dbUrl_, user_, pass_] :=
Module[{db = connectDb[dbUser, dbPass, dbUrl],
salt = RandomChoice[Range[32, 127], 16]},
If[MemberQ[SQLSelect[db, "users", {"username"}], {user}],
Message[createUser::nameTaken, user]; Return[]];
SQLInsert[db,
"users", {"username", "pass_salt", "pass_md5"}, {user,
SQLBinary[salt],
SQLBinary[
IntegerDigits[Hash[FromCharacterCode[salt] <> pass, "MD5"], 256,
16]]}]; CloseSQLConnection[db];];
authenticateUser[dbUser_, dbPass_, dbUrl_, user_, pass_] :=
Module[{db = connectDb[dbUser, dbPass, dbUrl], rtn},
rtn = MemberQ[SQLSelect[db, "users", {"username"}], {user}] &&
Module[{data =
SQLSelect[db, "users", {"username", "pass_salt", "pass_md5"},
SQLColumn["username"] == user][[1]]},
Hash[FromCharacterCode[data[[2, 1]]] <> pass, "MD5"] ==
FromDigits[data[[3, 1]], 256]]; CloseSQLConnection[db]; rtn];

Objeck[edit]

use ODBC;
use Encryption;
 
class SqlTest {
@conn : Connection;
 
function : Main(args : String[]) ~ Nil {
SqlTest->New()->Run();
}
 
New() {
@conn := Connection->New("test", "root", "helloworld");
}
 
method : Run() ~ Nil {
CreateUser("objeck", "beer");
AuthenticateUser("objeck", "beer");
leaving {
@conn->Close();
};
}
 
method : AuthenticateUser(username : String, password : String) ~ Nil {
status := false;
ps : ParameterStatement;
result : ResultSet;
if(@conn->IsOpen()) {
sql := "SELECT pass_salt, pass_md5 FROM users WHERE username = ?";
ps := @conn->CreateParameterStatement(sql);
ps->SetVarchar(1, username);
 
result := ps->Select();
if(result <> Nil & result->Next()) {
salt_buffer := Byte->New[16];
result->GetBlob(1, salt_buffer);
salt := "";
for(i := 0; i < 16; i+=1;) {
salt->Append(salt_buffer[i]);
};
 
db_password_buffer := Byte->New[16];
result->GetBlob(2, db_password_buffer);
 
password->Append(salt);
user_password_buffer := Hash->MD5(password->ToByteArray());
 
IO.Console->Print("user: authenticated=")->PrintLine(IsEqual(db_password_buffer, user_password_buffer));
};
 
};
 
leaving {
if(ps <> Nil) {
ps->Close();
};
 
if(ps <> Nil) {
ps->Close();
};
};
}
 
method : CreateUser(username : String, password : String) ~ Nil {
salt := "";
for(i := 0; i < 16; i+=1;) { salt->Append((Float->Random() * 100)->As(Int)); };
salt := salt->SubString(16);
 
password->Append(salt);
md5_password := Hash->MD5(password->ToByteArray());
 
ps : ParameterStatement;
if(@conn->IsOpen()) {
sql := "INSERT INTO users(username, pass_salt, pass_md5) VALUES (?, ?, ?)";
ps := @conn->CreateParameterStatement(sql);
ps->SetVarchar(1, username);
ps->SetBytes(2, salt->ToByteArray());
ps->SetBytes(3, md5_password);
 
IO.Console->Print("adding user: username=")->Print(username)
->Print(", salt=")->Print(salt)
->Print(", status=")->PrintLine(ps->Update());
};
 
leaving {
if(ps <> Nil) {
ps->Close();
};
};
}
 
method : IsEqual(left : Byte[], right : Byte[]) ~ Bool {
if(left->Size() <> right->Size()) {
return false;
};
 
each(i : left) {
if(left[i] <> right[i]) {
return false;
};
};
 
return true;
}
}

Perl[edit]

Works with: Perl version 5.8.8
Library: DBI

use DBI;
 
# returns a database handle configured to throw an exception on query errors
sub connect_db {
my ($dbname, $host, $user, $pass) = @_;
my $db = DBI->connect("dbi:mysql:$dbname:$host", $user, $pass)
or die $DBI::errstr;
$db->{RaiseError} = 1;
$db
}
 
# if the user was successfully created, returns its user id.
# if the name was already in use, returns undef.
sub create_user {
my ($db, $user, $pass) = @_;
my $salt = pack "C*", map {int rand 256} 1..16;
$db->do("INSERT IGNORE INTO users (username, pass_salt, pass_md5)
VALUES (?, ?, unhex(md5(concat(pass_salt, ?))))"
,
undef, $user, $salt, $pass)
and $db->{mysql_insertid} or undef
}
 
# if the user is authentic, returns its user id. otherwise returns undef.
sub authenticate_user {
my ($db, $user, $pass) = @_;
my $userid = $db->selectrow_array("SELECT userid FROM users WHERE
username=? AND pass_md5=unhex(md5(concat(pass_salt, ?)))"
,
undef, $user, $pass);
$userid
}

PHP[edit]

To use MySQL in PHP you need the php_mysql module installed

 
function connect_db($database, $db_user, $db_password, $host = 'localhost', $port = NULL, $die = false) {
// Returns a MySQL link identifier (handle) on success
// Returns false or dies() on error depending on the setting of parameter $die
// Parameter $die configures error handling, setting it any non-false value will die() on error
// Parameters $host, $port and $die have sensible defaults and are not usually required
 
if(!$db_handle = @mysql_connect($host.($port ? ':'.$port : ''), $db_user, $db_password)) {
if($die)
die("Can't connect to MySQL server:\r\n".mysql_error());
else
return false;
}
if(!@mysql_select_db($database, $db_handle)) {
if($die)
die("Can't select database '$database':\r\n".mysql_error());
else
return false;
}
return $db_handle;
}
 
function create_user($username, $password, $db_handle) {
// Returns the record ID on success or false on failure
// Username limit is 32 characters (part of spec)
if(strlen($username) > 32)
return false;
 
// Salt limited to ASCII 32 thru 254 (not part of spec)
$salt = '';
do {
$salt .= chr(mt_rand(32, 254));
} while(strlen($salt) < 16);
 
// Create pass_md5
$pass_md5 = md5($salt.$password);
 
// Make it all binary safe
$username = mysql_real_escape_string($username);
$salt = mysql_real_escape_string($salt);
 
// Try to insert it into the table - Return false on failure
if(!@mysql_query("INSERT INTO users (username,pass_salt,pass_md5) VALUES('$username','$salt','$pass_md5')", $db_handle))
return false;
 
// Return the record ID
return mysql_insert_id($db_handle);
}
 
function authenticate_user($username, $password, $db_handle) {
// Checks a username/password combination against the database
// Returns false on failure or the record ID on success
 
// Make the username parmeter binary-safe
$safe_username = mysql_real_escape_string($username);
 
// Grab the record (if it exists) - Return false on failure
if(!$result = @mysql_query("SELECT * FROM users WHERE username='$safe_username'", $db_handle))
return false;
 
// Grab the row
$row = @mysql_fetch_assoc($result);
 
// Check the password and return false if incorrect
if(md5($row['pass_salt'].$password) != $row['pass_md5'])
return false;
 
// Return the record ID
return $row['userid'];
}
 

Python[edit]

Works with: Python version 2.7

Uses the official Python MySQL connector

import mysql.connector 
import hashlib
 
import sys
import random
 
DB_HOST = "localhost"
DB_USER = "devel"
DB_PASS = "devel"
DB_NAME = "test"
 
def connect_db():
''' Try to connect DB and return DB instance, if not, return False '''
try:
return mysql.connector.connect(host=DB_HOST, user=DB_USER, passwd=DB_PASS, db=DB_NAME)
except:
return False
 
def create_user(username, passwd):
''' if user was successfully created, returns its ID; returns None on error '''
db = connect_db()
if not db:
print "Can't connect MySQL!"
return None
 
cursor = db.cursor()
 
salt = randomValue(16)
passwd_md5 = hashlib.md5(salt+passwd).hexdigest()
 
# If username already taken, inform it
try:
cursor.execute("INSERT INTO users (`username`, `pass_salt`, `pass_md5`) VALUES (%s, %s, %s)", (username, salt, passwd_md5))
cursor.execute("SELECT userid FROM users WHERE username=%s", (username,) )
id = cursor.fetchone()
db.commit()
cursor.close()
db.close()
return id[0]
except:
print 'Username was already taken. Please select another'
return None
 
def authenticate_user(username, passwd):
db = connect_db()
if not db:
print "Can't connect MySQL!"
return False
 
cursor = db.cursor()
 
cursor.execute("SELECT pass_salt, pass_md5 FROM users WHERE username=%s", (username,))
 
row = cursor.fetchone()
cursor.close()
db.close()
if row is None: # username not found
return False
salt = row[0]
correct_md5 = row[1]
tried_md5 = hashlib.md5(salt+passwd).hexdigest()
return correct_md5 == tried_md5
 
def randomValue(length):
''' Creates random value with given length'''
salt_chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
 
return ''.join(random.choice(salt_chars) for x in range(length))
 
if __name__ == '__main__':
user = randomValue(10)
passwd = randomValue(16)
 
new_user_id = create_user(user, passwd)
if new_user_id is None:
print 'Failed to create user %s' % user
sys.exit(1)
auth = authenticate_user(user, passwd)
if auth:
print 'User %s authenticated successfully' % user
else:
print 'User %s failed' % user
 

Racket[edit]

#lang racket
(require db file/md5)
(define-logger authentication)
(current-logger authentication-logger)
 
(define DB-HOST "localhost")
(define DB-USER "devel")
(define DB-PASS "devel")
(define DB-NAME "test")
 
(define (connect-db)
(mysql-connect
#:user DB-USER
#:database DB-NAME
#:password DB-PASS))
 
(define (salt+password->hash salt password #:hex-encode? (hex-encode? #f))
(md5 (bytes-append salt password) hex-encode?))
 
(define (report-sql-error e)
(log-authentication-error "Failed to create user:~s" (exn-message e))
#f)
 
(define (create-user db username passwd)
 ; if user was successfully created, returns its ID else #f
(define salt (list->bytes (for/list ((i (in-range 16))) (random 256))))
(define hash (salt+password->hash salt passwd))
(with-handlers ((exn:fail:sql? report-sql-error))
(query db "INSERT INTO users (username, pass_salt, pass_md5) VALUES (?, ?, ?)"
username salt hash)))
 
(define (authenticate-user db username password)
(or
(match (query-maybe-row db "SELECT pass_salt, pass_md5 FROM users WHERE username = ?" username)
[#f #f]
[(vector salt hash) (bytes=? hash (salt+password->hash salt password))])
 ; don't let the deviants know whether it's the username or password that's dodgy
(error "the username, password combination does not exist in system")))
 
(module+ test
(require rackunit)
(define test-DB (connect-db))
 ; typically, you only do this the once (or risk upsetting your users bigtime!)
 ; call this just the once!
(define (create-users-table db)
(query-exec db "DROP TABLE IF EXISTS users")
(query-exec db #<<EOS
CREATE TABLE users (
userid INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(32) UNIQUE KEY NOT NULL,
pass_salt tinyblob NOT NULL,
-- a string of 16 random bytes
pass_md5 tinyblob NOT NULL
-- binary MD5 hash of pass_salt concatenated with the password
);
EOS
))
(create-users-table test-DB)
(create-user test-DB #"tim" #"shh! it's a secret!")
 ; ensure the user exists (for testing purposes)
(check-match (query-list test-DB "SELECT userid FROM users WHERE username = 'tim'") (list _))
 ; (ah... but tim exists!!!)
(check-false (create-user test-DB #"tim" #"tim's password"))
(check-exn exn:fail? (λ () (authenticate-user test-DB #"tim" #"password")))
(check-true (authenticate-user test-DB #"tim" #"shh! it's a secret!")))

Raven[edit]

MySQL connectivity is available out of the box. Below, we do not ever send the plain text password over the wire.

 'mysql:[email protected]/test' open as mysql
'abcdefghijklmnopqrstuvwxyz0123456789' as $salt_chars
 
# return userid for success and FALSE for failure.
define create_user use $user, $pass
group 16 each as i
$salt_chars choose chr
join as $pass_salt
"%($pass_salt)s%($pass)s" md5 as $pass_md5
$user copy mysql escape as $user_name
group 'INSERT IGNORE into users (username, pass_md5, pass_salt)'
" VALUES ('%($user_name)s', unhex('%($pass_md5)s'), '%($pass_salt)s')"
join mysql query inserted
 
# return userid for success and FALSE for failure.
define authenticate_user use $user, $pass
FALSE as $userid
$user copy mysql escape as $user_name
group 'SELECT userid, pass_salt, hex(pass_md5)'
" FROM users WHERE username = '%($user_name)s'"
join mysql query as rs
rs selected
if rs fetch values into $possible_userid, $pass_salt, $pass_md5
"%($pass_salt)s%($pass)s" md5 $pass_md5 lower =
if $possible_userid as $userid
$userid
 
'foo' 'bar' create_user  !if "could not create user\n" print bye
'foo' 'bar' authenticate_user !if "could not authenticate user\n" print bye
 
"user successfully created and authenticated!\n" print

Sidef[edit]

Translation of: Perl
require('DBI')
 
# returns a database handle configured to throw an exception on query errors
func connect_db(dbname, host, user, pass) {
var db = %s<DBI>.connect("dbi:mysql:#{dbname}:#{host}", user, pass)
db || die (global DBI::errstr)
db{:RaiseError} = 1
db
}
 
# if the user was successfully created, returns its user id.
# if the name was already in use, returns nil.
func create_user(db, user, pass) {
var salt = "C*".pack(16.of { 256.irand }...)
db.do(
"INSERT IGNORE INTO users (username, pass_salt, pass_md5)
VALUES (?, ?, unhex(md5(concat(pass_salt, ?))))"
, nil, user, salt, pass
) ? db{:mysql_insertid} : nil
}
 
# if the user is authentic, returns its user id. otherwise returns nil.
func authenticate_user(db, user, pass) {
db.selectrow_array("SELECT userid FROM users WHERE
username=? AND pass_md5=unhex(md5(concat(pass_salt, ?)))"
,
nil, user, pass
)
}

Tcl[edit]

Works with: Tcl version 8.6

Also requires the TDBC driver for MySQL.

package require tdbc
 
proc connect_db {handleName dbname host user pass} {
package require tdbc::mysql
tdbc::mysql::connection create $handleName -user $user -passwd $pass \
-host $host -database $dbname
return $handleName
}
 
# A simple helper to keep code shorter
proc r64k {} {
expr int(65536*rand())
}
 
proc create_user {handle user pass} {
set salt [binary format ssssssss \
[r64k] [r64k] [r64k] [r64k] [r64k] [r64k] [r64k] [r64k]]
# Note that we are using named parameters below, :user :salt :pass
# They are bound automatically to local variables with the same name
$handle allrows {
INSERT IGNORE INTO users (username, pass_salt, pass_md5)
VALUES (:user, :salt, unhex(md5(concat(:salt, :pass))))
}
return ;# Ignore the result of the allrows method
}
 
proc authenticate_user {handle user pass} {
$handle foreach row {
SELECT userid FROM users WHERE
username=:user AND pass_md5=unhex(md5(concat(pass_salt, :pass)))
} {
return [dict get $row userid]
}
# Only get here if no rows selected
error "authentication failed for user \"$user\""
}