Making TurnKey more turnkey - the end to default passwords

In our quest to make the upcoming TurnKey 11.0 release more "turnkey", I set out to extend the firstboot inithooks to include application specific configuration hooks such as setting of the admin password, email and domain to serve (where applicable).

I'm glad to announce that the quest is now over, and that puts the end to default passwords.

But what about hosting/cloud deployment where the user doesn't have boot interaction? Well, all the configurations can be pre-seeded. We'll be adding support for preseeding to the Hub soon after the 11.0 release. Until then, the instances will use default passwords, but they can be easily changed by executing the inithooks directly.

While on my quest, it was interesting to get a birds eye view of how the different applications store their passwords, so I thought I'd share:

Comparison table (22 applications)

  clear crypt md4 md5 sha1 salt
tomcat x          
trac   x        
otrs   x        
twiki   x        
mldonkey     x      
joomla       x    
mantis       x    
wordpress       x    
dokuwiki       x    
phpbb       x    
extplorer       x    
gallery       x   x
deki       x   x
mediawiki       x   x
moodle       x   x
prestashop       x   x
magento       x   x
vtiger       x   x
bugzilla       x   x
roundup         x  
redmine         x  
django         x x

Foot notes

  • clear: Passwords are stored in clear text.
  • crypt: Passwords are hashed with crypt or htpasswd.
  • md4/md5/sha1: Passwords are put through a cryptographic hash function, which is a deterministic one-way procedure that takes a block of data and returns a fixed-size bit string.
  • salt: A salt (random bits) are added to the password before passing it through the hash function. Some of the applications use a randomly generated salt stored in a configuration file, others calculate it on the fly and add it the hash itself, while others use the user id as the salt. Using a salt is meant to add to the complexity and time it would take an attacker (who obtained the hashed passwords) to determine the original clear-text password.

Now for some code snippets

Just in case you're interested, here are some code snippets as well as the password storage locations. The full code will of course be included in the final 11.0 release.


doc = xml.dom.minidom.parse(TOMCAT_USERS).documentElement
users = doc.getElementsByTagName('user')
for user in users:
    if not user.getAttribute('username') == 'admin':

    user.setAttribute('password', password)
FILE: /etc/tomcat6/tomcat-users.xml


system("htpasswd -cb %s admin %s" % (authfile, password))
FILE: /etc/trac/htpasswd


hashpass = crypt.crypt(password, 'ro')    # 2 chars of username/email
MYSQL: otrs2.users pw


output = getoutput("htpasswd -bn foo %s" % password)
hashpass = output.split(":")[1].strip()
FILE: /var/www/twiki/data/.htpasswd


MD4_HASH=$(echo -n $PASSWORD | openssl dgst -md4 | tr [a-z] [A-Z])
FILE: /var/lib/mldonkey/users.ini


hashpass = hashlib.md5(password).hexdigest()
MYSQL: jos_db.jos_users password


hashpass = hashlib.md5(password).hexdigest()
MYSQL: mantis.mantis_user_table password


hashpass = hashlib.md5(password).hexdigest()
MYSQL: wordpress.wp_users user_pass


hashpass = hashlib.md5(password).hexdigest()
FILE: /var/lib/dokuwiki/acl/users.auth.php


hashpass = hashlib.md5(password).hexdigest()
MYSQL: phpbb3.phpbb_users user_password


MD5_HASH=$(echo -n $PASSWORD | md5sum | cut -d " " -f 1)
hashpass = hashlib.md5(password).hexdigest()
FILE: /var/www/extplorer/config/.htusers.php


salt = "".join(random.choice(string.letters) for line in range(4))
hashpass = salt + hashlib.md5(salt + password).hexdigest()
MYSQL: gallery2.g2_User g_hashedPassword


hashpass = hashlib.md5(password).hexdigest()
hashpass = hashlib.md5("1-" + hashpass).hexdigest()     # userid 1
MYSQL: wikidb.users user_password


hashpass = hashlib.md5(password).hexdigest()
hashpass = hashlib.md5("1-" + hashpass).hexdigest()     # userid 1
MYSQL: wiki_db.user user_password


for line in file(conffile).readlines():
    m = re.match("\$CFG->passwordsaltmain = '(.*)';", line)
    if m:
        salt =

hashpass = hashlib.md5(password + salt).hexdigest()
MYSQL: moodle.user SET password
CONFFILE: /var/www/moodle/config.php


for line in file(conffile).readlines():
    m = re.match("define\('_COOKIE_KEY_', '(.*)'", line)
    if m:
        cookie_key =

hashpass = hashlib.md5(cookie_key + password).hexdigest()
MYSQL: prestashop.employee passwd
CONFFILE: /var/www/prestashop/config/


salt = "".join(random.choice(string.letters) for line in range(2))
hashpass = hashlib.md5(salt + password).hexdigest() + ":" + salt
MYSQL: magento.admin_user SET password


hashpass = hashlib.md5(password).hexdigest()

$salt = substr($username, 0, 2);
$salt = '$1$' . str_pad($salt, 9, '0');
$encrypted_password = crypt($password, $salt);
MYSQL: vtigercrm.vtiger_users user_hash, user_password  


$salt = '';
for ( my $i=0 ; $i < 8 ; ++$i ) {
    $salt .= $saltchars[rand(64)];

$cryptedpassword = crypt($password, $salt);
MYSQL: bugzilla3.profiles cryptpassword


hashpass = "{SHA}" + hashlib.sha1(password).hexdigest()
MYSQL: roundup._user _password


hashpass = hashlib.sha1(password).hexdigest()
MYSQL: railsapp_production.users hashed_password


salt = hashlib.sha1(str(random.random())).hexdigest()[:5]
hash = hashlib.sha1(salt + password).hexdigest()
hashpass = 'sha1$%s$%s' % (salt, hash)
MYSQL: django_db.auth_user password


Adrian Moya's picture

Excellent work guys, looking good, I like also that domains are being added where applicable. I'm anxiously waiting for the beta of the new appliances! 

Alon Swartz's picture

It was a lot of work but I think it adds a lot to the user-experience and was worth the effort.

We'll be including Magento, PrestaShop and VTiger with the 11.0 release (coming shortly), as they are ready and no point in holding them back. I've also taken notes for feedback (for you and Basil) which I need to summarize - I'll try get that done soon. 

Adrian Moya's picture

I'm sure I'll learn a couple of lessons from that feedback. I'v already learned a lot helping this project. BTW, I'm missing your feedback on the tklpatch commands of the tkldevenv. I'll upload the entire patch to github so that when you are finally free of the 11pt1 release, you can easily pull/diff those. As you are the author of the original scripts, your feedback is invaluable to the future of that appliance. 

Alon Swartz's picture

Good idea regarding github, that will make it a lot easier to pull the code. It should also make your life simpler publishing updates, and tracking revisions (which I hope you're already doing).

I'll push out the feedback as soon as I can.

This is brilliant. And knowing how to do that is magical and genious from my point of view. I'm trying to make sense of the code snippets and not getting very far. I understand when values are being passed to MySQL - but take Redmine for example: is that line a part of a configuration file, or is that the value of the field in the MySQL table? Fascinated.

Alon Swartz's picture

Thanks Rik!

The code snippets only show how we take the clear text password and convert it into the desired hash for storage. The code displayed in the post are (mainly) taken from the /usr/lib/inithooks/bin/$, which is called from the related script in firstboot.d (which may preseed the answers).

Why the separation of code? Unix philosophy - do one thing, and do it well. Take a look (if you haven't already) at the inithook documentation.

To wet your appetite (as you asked so nicely), I've attached the full bin/

"""Set Redmine admin password and email

    --pass=     unless provided, will ask interactively
    --email=    unless provided, will ask interactively

import sys
import getopt
import hashlib

from dialog_wrapper import Dialog
from mysqlconf import MySQL

def usage(s=None):
    if s:
        print >> sys.stderr, "Error:", s
    print >> sys.stderr, "Syntax: %s [options]" % sys.argv[0]
    print >> sys.stderr, __doc__

def main():
        opts, args = getopt.gnu_getopt(sys.argv[1:], "h",
                                       ['help', 'pass=', 'email='])
    except getopt.GetoptError, e:

    password = ""
    email = ""
    for opt, val in opts:
        if opt in ('-h', '--help'):
        elif opt == '--pass':
            password = val
        elif opt == '--email':
            email = val

    if not password:
        d = Dialog('TurnKey Linux - First boot configuration')
        password = d.get_password(
            "Redmine Password",
            "Enter new password for the Redmine 'admin' account.")

    if not email:
        if 'd' not in locals():
            d = Dialog('TurnKey Linux - First boot configuration')

        email = d.get_email(
            "Redmine Email",
            "Enter email address for the Redmine 'admin' account.",

    hashpass = hashlib.sha1(password).hexdigest()

    m = MySQL()
    m.execute('UPDATE railsapp_production.users SET mail=\"%s\" WHERE login=\"admin\";' % email)
    m.execute('UPDATE railsapp_production.users SET hashed_password=\"%s\" WHERE login=\"admin\";' % hashpass)

if __name__ == "__main__":

I know how busy you are and really fully appreciate that you took the time to lay some of this out for me. I'm working through Python in a class, and am gonna enjoy digging in see mechanism. Thanks again,


Jeremy Davis's picture

Very tidy feature!

Alon Swartz's picture

I've been asked via email, on twitter as well on the forums when the 11.0 release is coming out, and I promised before the end of the year. We partly lived up to my promise. All 11.0 ISO's have been released, TKLBAM profiles updated, and Amazon Images (AMI's) uploaded.

And now, before the new year comes in, I pushed out a large update to the Hub which adds:

  • Support for all 11.0 appliances. 2009-10.2 appliances are still available under the legacy option.
  • Support for preseeding 11.0 appliances (launch form updates via AJAX). TKLBAM will also be initialized via preseeding so you don't need to copy/paste the Hub API Key.
  • Support for Asia Pacific - Singapore region (ap-southeast-1).
  • New help/FAQ section.
  • Minor bugfixes

We'll be making the official 11.0 announcement once the VM builds are ready and released, and in that announcement include more details on what has changed in the Hub.

Lastly, if you come across any issues with the Hub, please post a comment or send feedback via the Hub.

L. Arnold's picture

I am looking around the web for ways to import batches of customers into Magento and move cleartext passwords into hashed format.

I see your method for hashing the Admin password.

Is there a way, for instance in Open Office or MySQL to convert a clear password to the correct Hashed password?

The only other method I know it to tell customers they have been imported to the new system, and please follow the "lost password" link to set a new one.  This is somewhat untidy it seems.

Thanks for all the great work.  Awesome set of work here!

OnePressTech's picture

You can access your server vai webshell or webmin

See for port numbers and sample screenshots.


Tim (Managing Director - OnePressTech)

Jeremy Davis's picture

See the bug report here and in the forums here (with a workaround). However AFAIK this shouldn't affect SSH login, so perhaps the non-US keyboard is also a culpret...

My suggestion would be to either install with a US keyboard (if you have one) and then change the keyboard to whatever you are using. Or just use a very simple password initially (with keys that will work...), configure your non-US keyboard and then reset the password. I don't have instructions handy on how to set up your non-US keyboard but google should get you going, just keep in mind that TKL v12.x is based on Debian Squeeze...

Also FWIW I have run a poll to ask about setting timezone and/or non-US keyboard on first boot. It is also logged on the TKL Dev traker. There is also a bug report about non-US keyboard causing log in issues (although this seems to be mainly from the web browser).

Jeremy Davis's picture

I am all out of ideas and have no experience with Jenkins. Perhaps someone on that thread has some further ideas for you?


Add new comment