#!/usr/bin/perl
# $Header: /u/cvsroot/passme/passme,v 1.3 2001/02/26 09:51:37 mayoff Exp $

use POSIX qw(ceil);

$pieceType = 'character';

if ($ARGV[0] eq '-3') {
    $pieceType = '3';
    shift;
}

# The user wants the generated passphrase to have at least $desiredBits bits
# of entropy.

$bitsDesired = shift;

if ($bitsDesired !~ /^[0-9]+$/ || $bitsDesired == 0) {
    print STDERR "usage: $0 desired-bits\n";
    exit(1);
}

if ($pieceType eq '3') {
    @pieces = qw(
    ace act add ado ads aft age ago aim air all and ant any ape apt arm art
    ash ask asp ass ate awe awl bad bag ban bar bat bay bed bee beg bet bib
    bid big bin bit boa bob bog boo bow box boy bra bud bug bum bun bus but
    buy bye cab cam can cap car cat caw cod cog con coo cop cot cow cry cub
    cue cup cut dad dam day den dew did die dig dim din dip doe dog don dot
    dry dub dud due dug dye ear eat ebb eel egg ego eke elf elk elm end era
    ere erg err fag fan far fat fed fee fen few fib fig fin fit fix flu fly
    fob foe fog for fox fro fry fun gab gad gag gap gas gay gel gem get gig
    gin gnu god got gum gun gut guy had hag ham hap has hat hay hem hen her
    hex hey hid him hip his hit hoe hog hop hot how hub hug hum hut ice icy
    ill imp ink inn ion ire irk its ivy jab jam jar jaw jay jet jig job jog
    jot joy jug jut ken key kid kin kit lab lad lag lap law lax lay led lee
    leg let lid lie lip lit lot low mad man map mat men met mew mid mix mob
    moo mop mud mug nab nag nap nay net new nil nip nod non nor not now nun
    nut oaf oak oar oat odd ode off oft ohm oil old opt orb ore out owe owl
    own pad pal pan par pat paw pay peg pen pep per pet pew phi pie pig pin
    pip pit ply pod pop pot pox pro pry pub pun pup pus put quo rag ram ran
    rap rat raw ray red rho rib rid rig rim rip rob rod rot rub rue rug rum
    run rut rye sad sag sap sat saw sax say sea see set sex she shy sip sir
    sit six ski sky sly sob sod son soy spa spy sub sue sum sun tab tag tan
    tap tar tau tax ten the tie tin tip tit ton top toy try tub tug ugh urn
    use van vat vex via vie vow wag wan war was wax way web wee wet who why
    wig win wit woe woo yes yet yon zoo
    );

    $joiner = ' ';
}

else {
    @pieces = 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
	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
	0 1 2 3 4 5 6 7 9 . -
    );

    $joiner = '';
}

# If we pick a completely random piece from @pieces, we get $bitsPerPiece
# bits of entropy.

$bitsPerPiece = log(scalar(@pieces))/log(2);

# So we need $piecesNeeded pieces...

$piecesNeeded = ceil($bitsDesired/$bitsPerPiece);

@passpieces = ();

open(RANDOM, "/dev/random") || die "/dev/random: $!\n";

# $random is constructed by reading bytes from RANDOM.

$random = 0;

# $entropy is the number of bits of entropy contained in $random.

$entropy = 0;

while ($piecesNeeded > 0) {

    # $random needs to contain at least as many bits of entropy as
    # a random piece choice requires.

    while ($entropy < $bitsPerPiece) {
	# Use sysread so we read only as much as we need and don't
	# leave any wasted in a buffer at exit.
	sysread(RANDOM, $byte, 1) || die "/dev/random: $!\n";

	$random = $random * 256 + ord($byte);
	$entropy += 8;
    }

    # Add a random piece to the passphrase.

    $pieceNumber = $random % scalar(@pieces);
    push(@passpieces, $pieces[$pieceNumber]);

    # Remove the used up bits of entropy from $random.

    $random = int($random / scalar(@pieces));
    $entropy -= $bitsPerPiece;

    $piecesNeeded--;
}

print join($joiner, @passpieces), "\n";

exit(0);

__END__

=head1 NAME

passme - Create random passwords or passphrases

=head1 SYNOPSIS

B<passme> [B<-3>] desired-bits

=head1 DESCRIPTION

I<passme> creates random passwords. The program strings together random
letters, digits, dashes, and periods (64 possible characters) to create
a password with at least I<desired-bits> bits of entropy. The program
reads exactly C<ceil(desired-bits/8)> bytes from C</dev/random> and
uses them to choose the characters in the password.

Assuming that C</dev/random> produces truly random bytes, a random
character from the program's set contains exactly 6 bits of entropy. A
66-bit password contains 11 characters. A 132-bit password contains 22
characters.

The set of characters could be easily expanded to include all 94
printable ASCII characters (or 95 including spaces). However, this would
only reduce the length of a 65-bit password to 10 characters, and of a
131-bit password to 20 characters.

=head1 OPTIONS

=over 5

=item B<-3>

When given this flag, the program creates a passphrase built from a
hard-coded list of 405 three-letter words.  The list does not contain
any words that are homonyms of other three-letter words.  You may find
this passphrase easier to remember than a password otherwise
generated.

A random word from the program's list contains approximately 8.66 bits
of entropy.  A passphrase of 8 words has about 69 bits of entropy.  A
passphrase of 15 words has about 130 bits of entropy.

=head1 AUTHOR

Rob Mayoff <mayoff@dqd.com>

=head1 VERSION

$Id: passme,v 1.3 2001/02/26 09:51:37 mayoff Exp $

=cut

