#!/usr/bin/env perl # # Copyright (c) 2022, 2023 Omar Polo # Copyright (c) 2023 Alexander Arkhipov # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. use strict; use warnings; use v5.32; use open ":std", ":encoding(UTF-8)"; use Getopt::Long qw(:config bundling require_order); use File::Basename; my $urandom; # opened later my $chars = "\x20-\x7E"; my $wordlist; my $length = 32; my $me = basename $0; sub usage { say STDERR "usage: $me [-an] [-w wordlist] [len]"; exit(1); } # not really arc4random but closer... sub arc4random { my $r = read($urandom, my $buf, 4) or die "$me: failed to read /dev/urandom: $!\n"; die "$me: short read\n" if $r != 4; return unpack('L', $buf); } # Calculate a uniformly distributed random number less than $upper_bound # avoiding "modulo bias". # # Uniformity is achieved by generating new random numbers until the one # returned is outside the range [0, 2**32 % $upper_bound). This # guarantees the selected random number will be inside # [2**32 % $upper_bound, 2**32) which maps back to [0, $upper_bound) # after reduction modulo $upper_bound. sub randline { my $upper_bound = shift; return 0 if $upper_bound < 2; my $min = 2**32 % $upper_bound; # This could theoretically loop forever but each retry has # p > 0.5 (worst case, usually far better) of selecting a # number inside the range we need, so it should rarely need # to re-roll. my $r; while (1) { $r = arc4random; last if $r >= $min; } return $r % $upper_bound; } GetOptions( "a" => sub { $chars = "0-9a-zA-Z" }, "n" => sub { $chars = "0-9" }, "w=s" => \$wordlist, ) or usage; $length = 6 if defined $wordlist; $length = shift if @ARGV; die "$me: invalid length: $length\n" unless $length =~ /^\d+$/; open($urandom, "<:raw", "/dev/urandom") or die "$me: can't open /dev/urandom: $!\n"; if (not defined $wordlist) { my $pass = ""; my $l = $length; while ($l >= 0) { read($urandom, my $t, 128) or die "$me: failed to read /dev/urandom: $!\n"; $t =~ s/[^$chars]//g; $l -= length($t); $pass .= $t; } say substr($pass, 0, $length); exit 0; } open(my $fh, "<", $wordlist) or die "$me: can't open $wordlist: $!\n"; my @lines = (0); push @lines, tell $fh while <$fh>; while ($length--) { seek $fh, $lines[randline scalar(@lines)], 0 or die "$me: seek: $!\n"; my $line = <$fh>; chomp($line); print $line; print " " if $length; } say "";