Friday, December 23, 2011

Generate Secure Passwords with Linux

One of the easiest ways to increase your security while browsing the net is to review the quality of your passwords.  It's not difficult, and it doesn't take long. 

After doing the obvious things to increase security, such as using firewalls and internet security packages, you may want to look at your passwords.  There are two big no no's when it comes to passwords, using simple ones, and using the same one for everything.  I'll bet some of you are doing one of these things, and who can blame you.  I have maybe 30 to 40 different logins and remembering unique, complex passwords for all of them is near impossible.  Even if you can remember them, you may mix up passwords between accounts and get shut out after three attempts.  So it becomes easy to use one simple password for everything, which leaves your logins vulnerable.  Whereas using secure passwords and securing a password list for all these accounts is something you can control and is manageable.  (How to do this elegantly is an ongoing longterm project I am working on)

The reason for having complex passwords is pretty obvious.  If an attacker is trying to break a password by guessing, it's going to take a much longer to guess a password that's 15 characters and has punctuation symbols in it, than guessing an 8 character password with only letters.  This task is done by software and can be done rather quickly in some cases.  There are other reasons for having a complex password relating to how they are stored in hashes.  Short passwords using a small symbol set can be recovered from their hash using pre-compiled rainbow tables.

Now let's say that you have chosen a completely random password that no-one would ever guess and you decide to use it for everything.  If one of the sites that you use this password on were to have poor security and your password was discovered, the attacker has access to all your accounts.  Not a good thing.

Some people have schemes like replacing the letters with other symbols, such as "MY 5E(RET P@55W0RD", these are safer but still not secure.  There is a pretty good chance whatever secret scheme you come up with has been thought of by someone before.  There's some good information on Wikipedia about passwords. en.wikipedia.org/wiki/Password_strength & en.wikipedia.org/wiki/Password are worth a read.

Ideally a password should be as long as possible and made up of randomly selected symbols from an allowed set.  In most cases the allowed set will be made up of a subset of the characters that appear on your keyboard, and randomly selecting them can be a hard task.  For this reason I put together a python script that would generate random passwords for me.  It's a bit hacky but gets the job done.  For a bit of fun I thought I would make it Unicode compatible too.

#! /usr/bin/env python
# -*- coding: utf-8 -*-

import argparse
import struct
import sys
import codecs
import math

#Custom print function to encode output in UTF-8
def UTFprint(in_string):
    in_string = in_string.encode('utf-8')
    sys.stdout.write(in_string)
    sys.stdout.write("\n")
    return

#Create command line parser
parser = argparse.ArgumentParser(description='Generate a password')
parser.add_argument('length', type=int, help='number of symbols in the password')
parser.add_argument('groups', type=str, help='groupcodes l=lower case letters, L=upper case letters, n=numbers, s=space, h=lower case hex, H=upper case hex, g=lower case greek, G=upper case greek, p=punctuation symbols, r=runes, e=high ASCII, k=keyboard symbols, c=CP437')
parser.add_argument('extras', type=str, help='extra symbols')
parser.add_argument('separate', type=str, help='separator string')
parser.add_argument('sepspace', type=int, help='number of symbols between separators')
parser.add_argument('-v', '--verbose', default=0, action='store_const', const=1, help='verbose output')
parser.add_argument('-r', '--random', default=0, action='store_const', const=1, help='when selected uses dev/random instead of dev/urandom')
args = parser.parse_args()

#define symbol strings
L_str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' #upper case letters
l_str = 'abcdefghijklmnopqrstuvwxyz' #lower case letters
n_str = '0123456789' #numbers
s_str = ' ' #space
H_str = '0123456789ABCDEF' #upper case hex
h_str = '0123456789abcdef' #lower case hex
g_str = 'αβγδεζηθικλμνξοπρστυφχψω'.decode('utf8') #lower case greek
G_str = 'ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ'.decode('utf8') #upper case greek
p_str = '!"#$%\'&()*+,-./;:<=>?@[\]^`_{}|~' #punctuation symbols
r_str = 'ᚠᚡᚢᚣᚤᚥᚦᚨᚩᚪᚫᚬᚭᚮᚯᚰᚱᚲᚳᚴᚵᚶᚷᚸᚹᚺᚻᚼᚽᚾᚿᛀᛁᛂᛃᛄᛅᛆᛇᛈᛉᛊᛋᛌᛍᛎᛏᛐᛑᛒᛓᛔᛕᛖᛗᛘᛙᛚᛛᛜᛝᛞᛟᛠᛡᛢᛣᛤᛥᛦᛧᛨᛩᛪᛮᛯᛰ'.decode('utf8') #runes
e_str = '☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┛┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■  '.decode('utf8') #high ASCII
k_str = l_str + L_str + s_str + n_str + p_str #keyboard symbols
c_str = e_str + k_str #CP437

#Assemble character list from command line options
sym_list = ''
if args.groups.count('L') > 0:
    sym_list = sym_list + L_str
if args.groups.count('l') > 0:
    sym_list = sym_list + l_str
if args.groups.count('s') > 0:
    sym_list = sym_list + s_str
if args.groups.count('n') > 0:
    sym_list = sym_list + n_str
if args.groups.count('H') > 0:
    sym_list = sym_list + H_str
if args.groups.count('h') > 0:
    sym_list = sym_list + h_str
if args.groups.count('g') > 0:
    sym_list = sym_list + g_str
if args.groups.count('G') > 0:
    sym_list = sym_list + G_str
if args.groups.count('p') > 0:
    sym_list = sym_list + p_str
if args.groups.count('r') > 0:
    sym_list = sym_list + r_str
if args.groups.count('k') > 0:
    sym_list = sym_list + k_str
if args.groups.count('e') > 0:
    sym_list = sym_list + e_str
if args.groups.count('c') > 0:
    sym_list = sym_list + c_str


#Add extra symbols to the symbol list
sym_list = sym_list + args.extras.decode('utf8')

#Sort list and remove duplicate symbols
sym_list = ''.join(sorted(set(sym_list)))

#If the space between separator strings is 0,
#set the separator to null
separator = args.separate.decode('utf8')
if args.sepspace == 0:
    separator = ''

#Set parameter variables
num_syms = len(sym_list)
pass_length = args.length
bits_per_symbol = int(math.ceil(math.log(num_syms,2)))

#Declare output variable
password = ""

#Set loop counters
symbols_gathered = 0
separator_count = 0

#/dev/random warning
UTFprint("")
if args.random == 1:
    UTFprint("/dev/random has been selected and may take")
    UTFprint("some time. To decrease the generation time")
    UTFprint("type some random data in another prompt.")
    UTFprint("")


#Read characters from /dev/urandom and if they are suitable
#use them to add symbols to the password
if args.random == 1:
    f = open("/dev/random","rb")
else:
    f = open("/dev/urandom","rb")

while (symbols_gathered < pass_length):
    rnd_str1 = f.read(1)
    rnd_str2 = f.read(1)
    rand_int1 = struct.unpack('B', rnd_str1)[0]
    rand_int2 = struct.unpack('B', rnd_str2)[0]
    rand_int = rand_int1 * 256 + rand_int2
    rand_int = rand_int % pow(2, bits_per_symbol)
    if (rand_int < num_syms):
        if (separator_count == args.sepspace):
            password = password + separator
            separator_count = 0
        password = password + sym_list[rand_int]
        symbols_gathered = symbols_gathered + 1
        separator_count = separator_count + 1
f.close()

#Output the results
if args.verbose == 1:
#    if args.random == 1:
#        UTFprint("/dev/random has been selected and may take")
#        UTFprint("some time. To decrease the generation time")
#        UTFprint("type some random data in another prompt.")
    UTFprint("Symbol Space:           " + str(num_syms))
    UTFprint("Password Length:        " + str(pass_length))
    UTFprint("Symbol List:            " + "<" + sym_list + ">")
    UTFprint("Separator String:       " + "<" + separator + ">")
    UTFprint("Password:               <" + password + ">")
    UTFprint("")
else:
    UTFprint(password)


Pretty basic.  It allows me to do things like use different sets of characters and add separators to the passwords as well.  Below are a few basic usage examples.

pass_gen.py examples

The syntax is pretty easy: number of characters to generate, symbol sets, extra characters, separator characters, characters between separators, and options -v for verbose and -r for /dev/random instead of /dev/urandom.

Example 1 is 16 symbols long from a hexadecimal character set, a colon every 4 symbols, with /dev/random and verbose
Example 2 is 16 symbols long using numbers, with tick and cross as extra symbols, a circle separator every 4 symbols, and verbose
Example 3 is 16 keyboard symbols with  no separators
Example 4 is 30 rune symbols with no separators
Example 5 is 30 small Greek letters with no separators

Here is a link to the file. pass_gen.py  With all the quirks of different shells and flavours of linux the unicode stuff might not work too well, most cases it should be a matter of tweaking things.

After I wrote the script I saw a simple command on Hak5 that can generate a password.  It's functionality covers pretty much any password you would need to create, and is so much simpler.

/dev/urandom | tr -dc A-Za-z0-9_ | head -c8 ; echo

 With a bit of modification I came up with this that generates multiple passwords.

cat /dev/urandom | tr -dc '[a-z][A-Z][0-9]-_!@#$%^&*()_+{}|:?=' | fold -w 10| head -n 5

4uJM0FV2Bc
Jx!CA0q3r3
sI@rR0PiC1
SPVJ{+d$b_
MUWm@&+Uuv

See how easy it is to create strong passwords.  This is of course no guarantee that you wont have one of your accounts compromised in the future, but it's a simple step that will make your account more secure and less attractive to hackers.

No comments:

Post a Comment