11
\$\begingroup\$

Here is my basic password generator which saves your password to a .txt file which you can choose the name of etc.

import random
import string
import os.path
#random numbers section
num1 = random.randrange(100,999)
number_section =  num1
#random letters section
string.letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()'
let1 = random.choice(string.letters)
let2 = random.choice(string.letters)
let3 = random.choice(string.letters)
let4 = random.choice(string.letters)
let5 = random.choice(string.letters)
let6 = random.choice(string.letters)
let7 = random.choice(string.letters)
let8 = random.choice(string.letters)
letters_section = let1 + let2 + let3 + let4 + let5 + let6 + let7 + let8
#letters and numbers combination
password = str(number_section) + letters_section
#input section
password_for = raw_input('This password is for: ')
your_pass =  'Your password for' + ' ' + password_for + ' ' + 'is:' + ' ' + password
print your_pass
#save section
save_path = 'C:\Users\Charles\Desktop\Passes'
name_of_file = raw_input("What is the name of the file: ")
completeName = os.path.join(save_path, name_of_file+".txt")         
file1 = open(completeName, "w")
toFile = your_pass
file1.write(toFile)
file1.close()

This will make a .txt file with a line such as:

Your password for IBM bluemix is: 451juoxQYky

How can I make it 'stronger'? Are there any major holes in it? How can I make it more compact?

This is just a personal project I'm doing to improve my python knowledge.

\$\endgroup\$
  • 2
    \$\begingroup\$ No reason all passwords shouldn't be random 32 char strings now. (use a password manager!) \$\endgroup\$ – bjb568 Aug 22 '15 at 16:16
  • 3
    \$\begingroup\$ The best thing you could do to make it stronger is not save your passwords to file in plain text. \$\endgroup\$ – 101 Aug 22 '15 at 21:58
  • 3
    \$\begingroup\$ @bjb568 no matter how many chars you use, this password generator is only capable on creating as many password as the seed is big (not sure about python, but java is only 48 bits). and anyway random is not a module that should be used for security purposes. In this case, knowing the approximate date of creation of the password goes a long way in finding it. \$\endgroup\$ – njzk2 Aug 23 '15 at 0:30
  • 2
    \$\begingroup\$ Yes, and a general rule with security: don't write your only code, use somebody else's that's already been battle-tested. \$\endgroup\$ – bjb568 Aug 23 '15 at 0:49
19
\$\begingroup\$

By making it more Pythonic for one:

string.letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()'
let1 = random.choice(string.letters)
let2 = random.choice(string.letters)
let3 = random.choice(string.letters)
let4 = random.choice(string.letters)
let5 = random.choice(string.letters)
let6 = random.choice(string.letters)
let7 = random.choice(string.letters)
let8 = random.choice(string.letters)
letters_section = let1 + let2 + let3 + let4 + let5 + let6 + let7 + let8

should be:

''.join(random.choice(string.letters) for _ in range(length))

Now instead of a hard coded length you can ask the user for a length or easily change it by changing one variable instead of adding or deleting lines.

Instead of this:

file1 = open(completeName, "w")
toFile = your_pass
file1.write(toFile)
file1.close()

You want:

with open(completeName, "w") as file1:
    file1.write(your_pass)  # no need to assign a new variable

The with context manager will automatically handle closing the file for you.

This seems to be a pattern in your code but its wholly unnecessary:

num1 = random.randrange(100,999)
number_section =  num1

Just use num1 or save the output of randrange() to number_section directly there is no need for an intermediate variable.

So in the end you can simplify to:

import random
import string
import os.path


num1 = random.randrange(100,999)
string.letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()'

letters_section = ''.join(random.choice(string.letters) for _ in range(8))  # change 8 to a variable if you want to ask the user for a length or have it passed as an argument
password = str(number_section) + letters_section

password_for = raw_input('This password is for: ')
your_pass =  'Your password for {} is: {}'.format(password_for, password)
print(your_pass)

save_path = 'C:\Users\Charles\Desktop\Passes'
name_of_file = raw_input("What is the name of the file: ")
completeName = os.path.join(save_path, name_of_file+".txt")         
with open(completeName, "w") as file1:
    file1.write(your_pass)

Or even more simply:

def gen_password(length):
    choices = string.digits + string.punctuation + string.ascii_letters
    return ''.join(random.choice(choices) for _ in range(length))

>>> gen_password(13)
'7P~R6dSy<T)+Z'
>>> gen_password(20)
"Q{qHs6ahwj^EaT_'t,.7"
>>> gen_password(11)
'XDi49z+;^?='
|improve this answer|||||
\$\endgroup\$
  • 1
    \$\begingroup\$ Good answer, very elegant. Makes me consider learning python. \$\endgroup\$ – rpax Aug 23 '15 at 11:14
  • \$\begingroup\$ @rpax Python good practice will often lean towards easier to read/maintain/understand code rather than clever or fancy. Well written Python reads almost as easily as English sometimes. \$\endgroup\$ – kylie.a Aug 24 '15 at 2:17
  • \$\begingroup\$ don't use default random for passwords. Use random.SystemRandom() or secrets.choice() \$\endgroup\$ – jfs Feb 21 '17 at 18:32
9
\$\begingroup\$

Regarding the code (not making it stronger!):

You can get random letters let1, let2, etc... much easier, simply:

let1, let2, let3, let4, let5, let6, let7, let8 = random.sample(string.letters, 8)

See more about random.sample here. Note this will get different letters, so there won't be any repeats.


In fact, you could shorten it much more:

letters_section = "".join(random.sample(string.letters, 8))

Over here, you are defining, immediately, the letters_section variable by joining, straight away, the separate letters!


Depending on which Python version you are running, you should write to your file differently:

In Python 2.5 and higher, use with:

with open(completeName, "w") as file1:
    toFile = your_pass
    file1.write(toFile)

This will close the file itself, without you haiving to do so.

In Python 2.*, when adding to the file, you should put the actual writing in a try statement in case something goes wrong writing to the file:

file1 = open(completeName, "w")
try:
    toFile = your_pass
    file1.write(toFile)
finally: #close it no matter if it works or not
    file1.close()

When forming sentences, for the space after each word, simply add the ' ' within the previous (or next) print.

So change

your_pass =  'Your password for' + ' ' + password_for + ' ' + 'is:' + ' ' + password

to

your_pass =  'Your password for ' + password_for + ' is: ' + password

You've got:

num1 = random.randrange(100,999)
number_section =  num1

Why? Just have one variable:

number_section = random.randrange(100,999)

With these in mind, your code is now:

import random
import string
import os.path
#random numbers section
number_section = random.randrange(100,999)
#random letters section
string.letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()'
letters_section = "".join(random.sample(string.letters, 8))
#letters and numbers combination
password = str(number_section) + letters_section
#input section
password_for = raw_input('This password is for: ')
your_pass =  'Your password for ' + password_for + ' is: ' + password
print your_pass
#save section
save_path = 'C:\Users\Charles\Desktop\Passes'
name_of_file = raw_input("What is the name of the file: ")
completeName = os.path.join(save_path, name_of_file+".txt")         
with open(completeName, "w") as file1:
    toFile = your_pass
    file1.write(toFile)
|improve this answer|||||
\$\endgroup\$
  • \$\begingroup\$ random.sample is not the right function to use; it’ll get distinct characters. \$\endgroup\$ – Ry- Aug 23 '15 at 8:14
  • \$\begingroup\$ 8 is a magic number. \$\endgroup\$ – Mast Aug 23 '15 at 19:37
7
\$\begingroup\$
  • Don’t modify string.letters. It doesn’t belong to your module. Make your own constant instead.

  • Use random.SystemRandom to generate secure random values; weak randomness is not usually appropriate for a password.

  • Avoid shuffling variables around for no reason. For example, you should assign your random number to one variable only and use it throughout.

  • Be aware that randrange has an exclusive upper bound. Unless you want numbers from 100 to 998, you should pass 1000.

  • Make use of functions. You can put password generation into a function.

  • Use string formatting to avoid str() and long concatenations.

import random
import os.path


PASSWORD_CHARACTERS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()'

def generate_password():
    generator = random.SystemRandom()

    number = generator.randrange(100, 1000)
    letters = ''.join(generator.choice(PASSWORD_CHARACTERS) for _ in range(8))

    return '%d%s' % (number, letters)


# Prompt
password_for = raw_input('This password is for: ')
password = generate_password()
print 'Your password for %s is: %s' % (password_for, password)

# Save password
save_path = r'C:\Users\Charles\Desktop\Passes'
name_of_file = raw_input('What is the name of the file: ')
complete_name = os.path.join(save_path, name_of_file + '.txt')

with open(complete_name, "w") as f:
    f.write(password)
|improve this answer|||||
\$\endgroup\$
3
\$\begingroup\$

So there are a few things you can do:

  1. Add a check to ensure that there are no runs of the same character families, i.e, no more than three upper case letters in a row.
  2. Add numbers
  3. This one will get some flak, increase the length of the actual password. This is up for debate because it is entropy, not the length of the password that makes it more secure.
|improve this answer|||||
\$\endgroup\$
1
\$\begingroup\$

With this class (below) obtains basic functionality.
Add some code to ask user (raw_input) and voilà.

import string
import random


class PassGen(object):
    """"""

    SAVE_PATH = r"c:\Users\{}\Desktop\Passes"
    CHARS = ''.join([string.digits, string.letters, string.punctuation])
    FILE_TEMPLATE = "Your password for IBM bluemix is: {}"

    def __init__(self, username, filename=None, password_length=8):
        self.username = username
        self.filename = "{}.txt".format(filename or self.username)
        self.password_length = password_length

    def password(self):
        """Return a random password in each execution."""
        return ''.join(random.sample(PassGen.CHARS, self.password_length))

    def make_file(self, password=None, optional_path=None):
        """Write the file to disk."""
        password = password or self.password()
        path = optional_path or PassGen.SAVE_PATH

        filename = os.path.join(path.format(self.username), self.filename)

        with open(filename, 'w+') as f:
            f.write(PassGen.FILE_TEMPLATE.format(password))

        print ">> File '{}' created".format(filename)

For the second basic question (stronger), give more length to password_length param

|improve this answer|||||
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.