aboutsummaryrefslogtreecommitdiffstats
path: root/makepass.pl
diff options
context:
space:
mode:
Diffstat (limited to 'makepass.pl')
-rwxr-xr-xmakepass.pl300
1 files changed, 228 insertions, 72 deletions
diff --git a/makepass.pl b/makepass.pl
index df8ad10..36bfcf1 100755
--- a/makepass.pl
+++ b/makepass.pl
@@ -10,22 +10,19 @@
use strict;
use warnings;
use v5.10.0;
-use feature qw(signatures);
-use constant {
- NORM_NUM => 10, # number of normal passwords
- SPEC_NUM => 6, # number of special passwords
- PASS_NUM => 6, # number of passphrases
+use Getopt::Std;
- MAX_LENGTH => 255, # max length of passwords
- RANGE_MAX => 42, # max length when using random length
- RANGE_MIN => 8, # min length when using random length
- PASS_WORDS => 8, # number of words in passphrases
+use constant {
+ MAX => 255, # max length of passwords
+ RANGE_MAX => 42, # max length when using random length
+ RANGE_MIN => 8, # min length when using random length
+ PASS_WORDS => 8, # number of words in passphrases
LOWER => 'abcdefghijklmnopqrstuvwxyz',
UPPER => 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
DIGIT => '0123456789',
- OTHER => '!$%&#/()=?+-_,.;:<>[]{}|\@*',
+ OTHER => '!$%&#/()=?+-_,.;:<>[]{}|@*',
};
my @lower = split //, LOWER;
@@ -36,7 +33,9 @@ my @alpha = ( @lower, @upper );
my @alnum = ( @alpha, @digit );
my @every = ( @alnum, @other );
-my $length = $ARGV[0] // $ENV{'MAKEPASS_DEFAULT_LENGTH'} // 0;
+my $length = $ENV{'MAKEPASS_LENGTH'} // 0;
+my $number = $ENV{'MAKEPASS_NUMBER'} // 10;
+my $printlen = 0;
my @normal =
$ENV{'MAKEPASS_NORMAL'}
? split //, $ENV{'MAKEPASS_NORMAL'}
@@ -47,99 +46,256 @@ my @special =
: (@every);
my $wordlist = $ENV{'MAKEPASS_WORDLIST'} || '/usr/share/dict/words';
-#
-# Some errorhandling
-#
-die "Length has to be a whole number between 0 and "
- . MAX_LENGTH
- . ". 0 means random length, and is the default."
- if ( $length !~ /^[[:digit:]]+$/ || $length < 0 || $length > MAX_LENGTH );
-
-die "The file \"$wordlist\" does not exist" if ( !-f $wordlist );
+my $col_width;
+my $col_num;
-#
-# Seed rand with data from /dev/random
-#
-# This is probably not necessary, but I do this in makepass.zsh and I want the
-# scripts to match
-open( my $random, '<:raw', '/dev/random' ) or die $!;
-read( $random, my $bytes, 8 ) and close($random);
-
-# See https://perldoc.perl.org/functions/pack
-srand( unpack( "L", $bytes ) );
-
-#
# Get screen width so we can print in pretty columns
-#
# TODO: Native perl. Possible solutions:
# - Term::ReadKey
# - not a builtin module. Needs installing everywhere.
# - builtin module ioctl and posix-call `TIOCGWINSZ`.
# - does not work on all systemd. I.e. OpenBSD.
-my $width = `tput cols` || 80;
-chomp($width); # remove \n at end of line
+my $xwidth = `tput cols` || 80;
+chomp($xwidth); # remove \n at end of line
#
-# Function to create random string
+# Main function! Go magic!
#
-sub randstring (@chars) {
- @chars = @normal if !@chars;
- my $len = $length || int( rand( RANGE_MAX - RANGE_MIN ) + RANGE_MIN );
- my $str;
+sub main {
+ #
+ # Getopts
+ #
- for my $i ( 1 .. $len ) {
- $str .=
- ( $i == 1 || $i == $len )
- ? $alnum[ rand @alnum ]
- : $chars[ rand @chars ];
- }
+ getopts('hl:n:p');
+ our ( $opt_h, $opt_l, $opt_n, $opt_p );
- return $str;
+ # -h = help
+ help() if ($opt_h);
+
+ # -l NUM = length of passwords
+ # I know this check is unnecessary because we check $LENGTH below, but
+ # I still want it.
+ die "-l takes a number between 0 and " . MAX
+ if ( $opt_l && !int_in_range( $opt_l, 0, MAX ) );
+ $length = $ARGV[0] // $opt_l // $length;
+
+ # -n NUM = number of passwords
+ die "-n takes a number between 0 and " . MAX
+ if ( $opt_n && !int_in_range( $opt_n, 1, MAX ) );
+ $number = $opt_n // $number;
+
+ # -p = print length of passwords
+ $printlen = 1 if $opt_p;
+
+ #
+ # Some errorhandling
+ #
+ die "only one argument" if ( scalar(@ARGV) > 1 );
+
+ die "length must be a number between 0 and " . MAX
+ unless ( int_in_range( $length, 0, MAX ) );
+
+ die "number-argument must be between 1 and " . MAX
+ unless ( int_in_range( $number, 1, MAX ) );
+
+ #
+ # Some other stuff
+ #
+
+ # Seed rand() with bits from /dev/random. Because.
+ open( my $random, '<:raw', '/dev/random' ) or die $!;
+ read( $random, my $bytes, 8 ) and close($random);
+
+ # See https://perldoc.perl.org/functions/pack
+ srand( unpack( "L", $bytes ) );
+
+ $printlen = $length < 100 ? 2 : 3 if $printlen;
+
+ $col_width = ( $length ? $length : RANGE_MAX ) + 2;
+ $col_num =
+ int( $xwidth / ( $col_width + ( $printlen ? $printlen + 1 : 0 ) ) ) || 1;
+
+ #
+ # Print passwords
+ #
+ print_columns( "Normal passwords", $number, @normal );
+ print "\n";
+ print_columns( "Passwords with special characters",
+ int( $number / 3 * 2 + 1 ), @special );
+
+ #
+ # Print Passphrases
+ #
+ # But only if we can read the wordlist
+ if ( -r $wordlist ) {
+ print "\n";
+ say "Passphrases:";
+ open( FILE, "<", $wordlist ) or die("Can't open file");
+ chomp( my @wordlist = <FILE> ) and close(FILE);
+
+ say passphrase( \@wordlist ) foreach ( 1 .. int( $number / 2 ) );
+ }
}
#
# Print in random strings in columns
#
-sub print_columns ( $title, $num, @chars ) {
- say "$title:";
-
+#sub print_columns ( $title, $num, @chars ) {
+sub print_columns {
+say "@_";
+ my $title = shift(@_);
+ my $num = shift(@_);
+ my @chars = @_;
my @strings;
+
push( @strings, randstring(@chars) ) foreach ( 1 .. $num );
- # Calculate the number of columns that can fit on the screen.
- my $length =
- ( $length || RANGE_MAX ) + 2; # Add two for spacing between columns
- my $columns = int( $width / $length ) || 1; # Minimum 1 col
+ say "$title:";
for my $i ( 1 .. $num ) {
- printf "%-${length}s%s",
- $strings[ $i - 1 ],
- ( $i % $columns == 0 ) ? "\n" : "";
- print "\n" if ( $i == $num && $i % $columns > 0 );
+ printf "%0${printlen}i ", length( $strings[ $i - 1 ] ) if ($printlen);
+ printf "%-${col_width}s", $strings[ $i - 1 ];
+ print "\n"
+ if ( $i % $col_num == 0 || ( $i == $num && $i % $col_num > 0 ) );
}
- print "\n";
}
#
-# Print passwords
+# Function to create random string
#
-print_columns( "Normal passwords", NORM_NUM, @normal );
-print_columns( "Passwords with special characters", SPEC_NUM, @special );
+#sub randstring (@chars) {
+sub randstring {
+ my @chars = @_;
+ my $str;
+ my $len = $length || int( rand( RANGE_MAX - RANGE_MIN ) + RANGE_MIN );
-#
-# Print Passphrases
-#
-say "Passphrases:";
-open( FILE, "<", $wordlist ) or die("Can't open file");
-chomp( my @wordlist = <FILE> ) and close(FILE);
+ @chars = @normal if !@chars;
+
+ $str .= $alpha[ rand @alpha ];
+ $str .= $chars[ rand @chars ] foreach ( 1 .. $len - 2 );
+ $str .= $alnum[ rand @alnum ] if ( $len >= 2 );
+
+ return $str;
+}
#
# Return passphrases
#
-sub passphrase ($arrh) { # Use array-handle to avoid copying large array around
+#sub passphrase ($arrh) {
+sub passphrase { # Use array-handle to avoid copying large array around
+ my $arrh = shift(@_);
my @indexes;
- push( @indexes, rand( @{$arrh} ) ) foreach ( 1 .. PASS_NUM );
- join( '-', @{$arrh}[@indexes] );
+
+ push( @indexes, rand( @{$arrh} ) ) foreach ( 1 .. PASS_WORDS );
+ ( my $str = join( '-', @{$arrh}[@indexes] ) ) =~ tr/A-Za-z0-9_-//cd;
+ return $str;
+}
+
+#
+# Check if int and in range
+#
+#sub int_in_range ( $num, $min, $max ) {
+sub int_in_range {
+ my $num = shift(@_);
+ my $min = shift(@_);
+ my $max = shift(@_);
+
+ return ( $num =~ /^[[:digit:]]+$/ && $min <= $num && $num <= $max ) ? 1 : 0;
+}
+
+#
+# Help function
+#
+sub help {
+ my $str='NAME
+ makepass - create several random passwords
+
+SYNOPSIS
+ makepass [OPTIONS] [NUM]
+
+ If a NUM is provided, passwords will be NUM characters long.
+
+ By default `makepass` will output passwords from the three following classes:
+
+ - Normal passwords - random strings with letters (both lower and upper
+ case), numbers, and dashes and underscores.
+
+ - Passwords with special characters - random strings generated from lower
+ and upper case letters, numbers, and the following characters:
+ !#$%&/()=?+-_,.;:<>[]{}|@*
+
+ - Passphrases - if we find a dictionary, a series of eight random words
+ from the dictionary, separated by dashes. The number of words can not be
+ changed, but you do not have to use all of them. Use as mane as you want.
+
+ The first character will always be alphabetic, and the last will always be
+ alphanumeric.
+
+DESCRIPTION
+ makepass has the following options:
+
+ -h
+ output this help-text
+ -l
+ length of passwords. See MAKEPASS_LENGTH below
+ -n
+ number of passwords. See MAKEPASS_NUMBER below
+ -p
+ print length of number
+
+ENVIRONMENT
+ makepass examines the following environmental variables.
+
+ MAKEPASS_LENGTH
+ Specifies the length of passwords. Valid values are 0-255. If 0, a
+ random value between 8 and 42 will be used for each password. -l
+ overrides this environmental variable, and the argument NUM overrides
+ that again. So `MAKEPASS_LENGTH=10 makepass -l 12 14` will give
+ passwords that are 14 characters long, even though both -l and
+ MAKEPASS_LENGTH also specifies a length.
+
+ MAKEPASS_NUMBER
+ The number of passwords to generate. This formula is used to determine
+ how many passwords from each group should be generated:
+ - (n) normal passwords
+ - (n / 3 * 2 + 1) special passwords
+ - (n / 2) passphrases
+ Where n is 10 by default. Valid values for n are 1-255. Floating-poing
+ math is not used, so results may vary.
+
+ MAKEPASS_PRINTLEN
+ If 1, print length of all passwords. If 0, don\'t.
+
+ MAKEPASS_NORMAL
+ String of characters from which to generate "normal" passwords.
+ Defaults to:
+ abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_
+
+ MAKEPASS_SPECIAL
+ String of characters from which to generate passwords with special
+ characters. Defaults to the same characters as in MAKEPASS_NORMAL, plus
+ these:
+ !#$%&/()=?+-_,.;:<>[]{}|@*
+
+ MAKEPASS_WORDLIST
+ Specifies the dictionary we find words for passphrases in. If this is
+ unset or empty, we try "/usr/share/dict/words". If that file does not
+ exist, no passphrases will be provided.
+
+NOTES
+ This scripts makes use of rand() - a perl function that returns a
+ pseudo-random number. It is not cryptographically secure. We initially
+ seed the random number generator with a random 32bit integer generated from
+ /dev/random. This should provide enough randomnes to generate sufficiently
+ secure passwords.
+
+AUTHOR
+ Dennis Eriksen <https://dnns.no>';
+
+ print "$str\n";
+
+ exit(0);
}
-say passphrase( \@wordlist ) foreach ( 1 .. PASS_NUM );
+# Call with & so it gets the same arguments as root. (@_)
+&main;