aboutsummaryrefslogtreecommitdiffstats
path: root/makepass.pl
diff options
context:
space:
mode:
authorDennis Eriksen <d@ennis.no>2023-09-01 09:13:05 +0200
committerDennis Eriksen <d@ennis.no>2023-09-01 09:13:05 +0200
commit8b44446e3a9a2cd85c298b6f47b440f2f6fce86b (patch)
tree54adc62ddbaf3507cfcf638b9292ecda238fb573 /makepass.pl
parentSplit makepass out of idgatt (diff)
downloadmakepass-8b44446e3a9a2cd85c298b6f47b440f2f6fce86b.tar.gz
adding perl-script
Diffstat (limited to '')
-rwxr-xr-xmakepass.pl141
1 files changed, 141 insertions, 0 deletions
diff --git a/makepass.pl b/makepass.pl
new file mode 100755
index 0000000..defe5fc
--- /dev/null
+++ b/makepass.pl
@@ -0,0 +1,141 @@
+#!/usr/bin/env perl
+#
+# Author : Dennis Eriksen <d@ennis.no>
+# File : makepass.pl
+# Created : 2023-07-27
+#
+
+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
+
+ 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
+
+ LOWER => 'abcdefghijklmnopqrstuvwxyz',
+ UPPER => 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
+ DIGIT => '0123456789',
+ OTHER => '!$%&#/()=?+-_,.;:<>[]{}|\@*',
+};
+
+my @lower = split //, LOWER;
+my @upper = split //, UPPER;
+my @digit = split //, DIGIT;
+my @other = split //, OTHER;
+my @alpha = ( @lower, @upper );
+my @alnum = ( @alpha, @digit );
+my @every = ( @alnum, @other );
+
+my $length = $ARGV[0] // $ENV{'MAKEPASS_DEFAULT_LENGTH'} // 0;
+my @normal =
+ $ENV{'MAKEPASS_NORMAL'}
+ ? split //, $ENV{'MAKEPASS_NORMAL'}
+ : ( @alnum, '-', '_' );
+my @special =
+ $ENV{'MAKEPASS_SPECIAL'}
+ ? split //, $ENV{'MAKEPASS_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 );
+
+#
+# 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
+
+#
+# Function to create random string
+#
+sub randstring (@chars) {
+ @chars = @normal if !@chars;
+ my $len = $length || int( rand( RANGE_MAX - RANGE_MIN ) + RANGE_MIN );
+ my $str;
+
+ for my $i ( 1 .. $len ) {
+ $str .=
+ ( $i == 1 || $i == $len )
+ ? $alnum[ rand @alnum ]
+ : $chars[ rand @chars ];
+ }
+
+ return $str;
+}
+
+#
+# Print in random strings in columns
+#
+sub print_columns ( $title, $num, @chars ) {
+ say "$title:";
+
+ my @strings = map { randstring(@chars) } 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
+
+ for my $i ( 1 .. $num ) {
+ printf "%-${length}s%s",
+ $strings[ $i - 1 ],
+ ( $i % $columns == 0 ) ? "\n" : "";
+ print "\n" if ( $i == $num && $i % $columns > 0 );
+ }
+ print "\n";
+}
+
+#
+# Print passwords
+#
+print_columns( "Normal passwords", NORM_NUM, @normal );
+print_columns( "Passwords with special characters", SPEC_NUM, @special );
+
+#
+# Print Passphrases
+#
+say "Passphrases:";
+open( FILE, "<", $wordlist ) or die("Can't open file");
+chomp( my @wordlist = <FILE> ) and close(FILE);
+
+#
+# Return passphrases
+#
+sub passphrase ($arrh) { # Use array-handle to avoid copying large array around
+ my @indexes = map { rand( @{$arrh} ) } 1 .. PASS_NUM;
+ join( '-', @{$arrh}[@indexes] );
+}
+
+say passphrase( \@wordlist ) foreach ( 1 .. PASS_NUM );