String::MkPasswd - random password generator


String-MkPasswd documentation Contained in the String-MkPasswd distribution.

Index


Code Index:

NAME

Top

String::MkPasswd - random password generator

SYNOPSIS

Top

  use String::MkPasswd qw(mkpasswd);

  print mkpasswd();

  # for the masochisticly paranoid...
  print mkpasswd(
      -length     => 27,
      -minnum     => 5,
      -minlower   => 1,   # minlower is increased if necessary
      -minupper   => 5,
      -minspecial => 5,
      -distribute => 1,
  );

ABSTRACT

Top

This Perl library defines a single function, mkpasswd(), to generate random passwords. The function is meant to be a simple way for developers and system administrators to easily generate a relatively secure password.

DESCRIPTION

Top

The exportable mkpasswd() function returns a single scalar: a random password. By default, this password is nine characters long with a random distribution of four lower-case characters, two upper-case characters, two digits, and one non-alphanumeric character. These parameters can be tuned by the user, as described in the "ARGUMENTS" section.

ARGUMENTS

The mkpasswd() function takes an optional hash of arguments.

-length

The total length of the password. The default is 9.

-minnum

The minimum number of digits that will appear in the final password. The default is 2.

-minlower

The minimum number of lower-case characters that will appear in the final password. The default is 2.

-minupper

The minimum number of upper-case characters that will appear in the final password. The default is 2.

-minspecial

The minimum number of non-alphanumeric characters that will appear in the final password. The default is 1.

-distribute

If set to a true value, password characters will be distributed between the left- and right-hand sides of the keyboard. This makes it more difficult for an onlooker to see the password as it is typed. The default is false.

-fatal

If set to a true value, mkpasswd() will Carp::croak() rather than return undef on error. The default is false.

If -minnum, -minlower, -minupper, and -minspecial do not add up to -length, -minlower will be increased to compensate. However, if -minnum, -minlower, -minupper, and -minspecial add up to more than -length, then mkpasswd() will return undef. See the section entitled "EXCEPTION HANDLING" for how to change this behavior.

EXCEPTION HANDLING

By default, mkpasswd() will return undef if it cannot generate a password. Some people are inclined to exception handling, so String::MkPasswd does its best to accomodate them. If the variable $String::MkPasswd::FATAL is set to a true value, mkpasswd() will Carp::croak() with an error instead of returning undef.

EXPORT

None by default. The mkpasswd() method is exportable.

SEE ALSO

Top

http://expect.nist.gov/#examples, mkpasswd(1)

AKNOWLEDGEMENTS

Top

Don Libes of the National Institute of Standards and Technology, who wrote the Expect example, mkpasswd(1).

AUTHOR

Top

Chris Grau <cgrau@cpan.org>

COPYRIGHT AND LICENSE

Top


String-MkPasswd documentation Contained in the String-MkPasswd distribution.

package String::MkPasswd;

use 5.006001;
use strict;
use base qw(Exporter);

use Carp qw(croak);

# Defaults.
use constant LENGTH		=> 9;
use constant MINNUM		=> 2;
use constant MINLOWER	=> 2;
use constant MINUPPER	=> 2;
use constant MINSPECIAL	=> 1;
use constant DISTRIBUTE	=> "";
use constant FATAL		=> "";

our %EXPORT_TAGS = (
	all	=> [ qw(mkpasswd) ],
);
our @EXPORT_OK = @{ $EXPORT_TAGS{all} };
our $VERSION = "0.03";
our $FATAL = "";

my %keys = (
	dist	=> {
		lkeys	=> [ qw(q w e r t a s d f g z x c v b) ],
		rkeys	=> [ qw(y u i o p h j k l n m) ],
		lnums	=> [ qw(1 2 3 4 5 6) ],
		rnums	=> [ qw(7 8 9 0) ],
		lspec	=> [ qw(! @ $ %), "#" ],
		rspec	=> [
			qw(^ & * ( ) - = _ + [ ] { } \ | ; : ' " < > . ? /), ","
		],
	},

	undist	=> {
		lkeys	=> [
			qw(a b c d e f g h i j k l m n o p q r s t u v w x y z)
		],
		rkeys	=> [
			qw(a b c d e f g h i j k l m n o p q r s t u v w x y z)
		],
		lnums	=> [ qw(0 1 2 3 4 5 6 7 8 9) ],
		rnums	=> [ qw(0 1 2 3 4 5 6 7 8 9) ],
		lspec	=> [
			qw(! @ $ % ~ ^ & * ( ) - = _ + [ ] { } \ | ; : ' " < > . ? /),
			"#", ","
		],
		rspec	=> [
			qw(! @ $ % ~ ^ & * ( ) - = _ + [ ] { } \ | ; : ' " < > . ? /),
			"#", ","
		],
	},
);

sub mkpasswd {
	my $class	= shift if UNIVERSAL::isa $_[0], __PACKAGE__;
	my %args	= @_;

	# Configuration.
	my $length		= $args{"-length"}     || LENGTH;
	my $minnum		= defined $args{"-minnum"}
		? $args{"-minnum"}
		: MINNUM;
	my $minlower	= defined $args{"-minlower"}
		? $args{"-minlower"}
		: MINLOWER;
	my $minupper	= defined $args{"-minupper"}
		? $args{"-minupper"}
		: MINUPPER;
	my $minspecial	= defined $args{"-minspecial"}
		? $args{"-minspecial"}
		: MINSPECIAL;
	my $distribute	= defined $args{"-distribute"}
		? $args{"-distribute"}
		: DISTRIBUTE;
	my $fatal		= defined $args{"-fatal"}
		? $args{"-fatal"}
		: FATAL;

	if ( $minnum + $minlower + $minupper + $minspecial > $length ) {
		if ( $fatal || $FATAL ) {
			croak "Impossible to generate $length-character password with "
					. "$minnum numbers, $minlower lowercase letters, "
					. "$minupper uppercase letters and $minspecial special "
					. "characters";
		} else {
			return;
		}
	}

	# If there is any underspecification, use additional lowercase letters.
	$minlower = $length - ($minnum + $minupper + $minspecial);

	# Choose left or right starting hand.
	my $initially_left = my $isleft = int rand 2;

	# Select distribution of keys.
	my $lkeys = $distribute ? $keys{dist}{lkeys} : $keys{undist}{lkeys};
	my $rkeys = $distribute ? $keys{dist}{rkeys} : $keys{undist}{rkeys};
	my $lnums = $distribute ? $keys{dist}{lnums} : $keys{undist}{lnums};
	my $rnums = $distribute ? $keys{dist}{rnums} : $keys{undist}{rnums};
	my $lspec = $distribute ? $keys{dist}{lspec} : $keys{undist}{lspec};
	my $rspec = $distribute ? $keys{dist}{rspec} : $keys{undist}{rspec};

	# Generate password.

	my @lpass = (undef) x $length;	# password chars typed by left hand
	my @rpass = (undef) x $length;	# password chars typed by right hand
	my ($left, $right);

	($left, $right) = &_psplit($minnum, \$isleft);
	for ( my $i = 0; $i < $left; $i++ ) {
		&_insert(\@lpass, $lnums->[rand @$lnums]);
	}
	for ( my $i = 0; $i < $right; $i++ ) {
		&_insert(\@rpass, $rnums->[rand @$rnums]);
	}

	($left, $right) = &_psplit($minlower, \$isleft);
	for ( my $i = 0; $i < $left; $i++ ) {
		&_insert(\@lpass, $lkeys->[rand @$lkeys]);
	}
	for ( my $i = 0; $i < $right; $i++ ) {
		&_insert(\@rpass, $rkeys->[rand @$rkeys]);
	}

	($left, $right) = &_psplit($minupper, \$isleft);
	for ( my $i = 0; $i < $left; $i++ ) {
		&_insert(\@lpass, uc $lkeys->[rand @$lkeys]);
	}
	for ( my $i = 0; $i < $right; $i++ ) {
		&_insert(\@rpass, uc $rkeys->[rand @$rkeys]);
	}

	($left, $right) = &_psplit($minspecial, \$isleft);
	for ( my $i = 0; $i < $left; $i++ ) {
		&_insert(\@lpass, $lspec->[rand @$lspec]);
	}
	for ( my $i = 0; $i < $right; $i++ ) {
		&_insert(\@rpass, $rspec->[rand @$rspec]);
	}

	# Merge results together.
	my $lpass = join "", map { defined $_ ? $_ : () } @lpass;
	my $rpass = join "", map { defined $_ ? $_ : () } @rpass;

	return $initially_left ? "$lpass$rpass" : "$rpass$lpass";
}

# Insert $char into password at a random position, thereby spreading the
# different kinds of characters throughout the password.
sub _insert {
	my $pass	= shift;	# ref = ARRAY
	my $char	= shift;

	my $pos;
	do {
		$pos = int rand(1 + @$pass);
	} while ( defined $pass->[$pos] );

	$pass->[$pos] = $char;
}

# Given a size, distribute between left and right hands, taking into account
# where we left off.
sub _psplit {
	my $max		= shift;
	my $isleft	= shift;	# ref = SCALAR

	my ($left, $right);

	if ( $$isleft ) {
		$right = int($max / 2);
		$left = $max - $right;
		$$isleft = !($max % 2);
	} else {
		$left = int($max / 2);
		$right = $max - $left;
		$$isleft = !($max % 2);
	}

	return ($left, $right);
}

1;

__END__