NAME
Farid::Model::Random provides a simple frontend to /dev/random
Webpage: <https://farid.ps/random>
SYNOPSIS
perl Random.pm [length]
use Farid::Model::Random;
print Farid::Model::Random->string([length], [charset]);
print Farid::Model::Random->alnum([length]);
print Farid::Model::Random->hex([length]);
print Farid::Model::Random->num([length]);
RATIONALE
Generating random tokens (e.g. passwords) is a common task but
implementations often get it wrong.
Common mistakes include using "rand" in perlfunc or String::Random which
returns pseudo-random data based on a seed value or modulo arithmetic
like "$byte % $n" and scaling methods like "int($byte / 255 * $n)" which
introduce bias unless 256 is a multiple of $n
Other implementations like Bytes::Random::Secure come with their own
implementation of a Pseudo-Random Number Generator which makes them
rather large and difficult to audit.
IMPLEMENTATION
This implementation is intentionally small (less than 100 lines of code)
to make auditing straightforward.
The module also exposes its own POD and source code at runtime, thereby
serving as library, CLI tool, documentation, and source distribution.
It relies on the Linux kernel's implementation of a CSPRNG, which - as
of Linux 5.6 - uses the ChaCha20 algorithm to implement a DRBG. Once the
initial entropy pool has been seeded, both /dev/random and /dev/urandom
draw from the same DRBG and no longer block.
It intentionally reads only 32 bytes at a time to allow the kernel to
replenish entropy between reads, reducing exposure if the DRBG's
internal state were ever leaked.
LICENSE
Provided as-is under GPLv3. No warranties. Use at your own risk.
alnum: FsiDCBJt
hex: cb1b36e5
num: 17117900
#!/usr/bin/perl
package Farid::Model::Random;
=head1 NAME
Farid::Model::Random provides a simple frontend to /dev/random
Webpage: L<https://farid.ps/random>
=head1 SYNOPSIS
perl Random.pm [length]
use Farid::Model::Random;
print Farid::Model::Random->string([length], [charset]);
print Farid::Model::Random->alnum([length]);
print Farid::Model::Random->hex([length]);
print Farid::Model::Random->num([length]);
=head1 RATIONALE
Generating random tokens (e.g. passwords) is a common task but implementations often get it wrong.
Common mistakes include using L<perlfunc/rand> or L<String::Random> which returns pseudo-random data based on a seed value or modulo arithmetic like C<$byte % $n> and scaling methods like C<int($byte / 255 * $n)> which introduce bias unless 256 is a multiple of $n
Other implementations like L<Bytes::Random::Secure> come with their own implementation of a Pseudo-Random Number Generator which makes them rather large and difficult to audit.
=head1 IMPLEMENTATION
This implementation is intentionally small (less than 100 lines of code) to make auditing straightforward.
The module also exposes its own POD and source code at runtime, thereby serving as library, CLI tool, documentation, and source distribution.
It relies on the Linux kernel's implementation of a CSPRNG, which - as of Linux 5.6 - uses the ChaCha20 algorithm to implement a DRBG.
Once the initial entropy pool has been seeded, both /dev/random and /dev/urandom draw from the same DRBG and no longer block.
It intentionally reads only 32 bytes at a time to allow the kernel to replenish entropy between reads, reducing exposure if the DRBG's internal state were ever leaked.
=head1 LICENSE
Provided as-is under GPLv3. No warranties. Use at your own risk.
=cut
use IO::File;
use Pod::Text;
use v5.20;
use feature 'signatures';
use warnings;
my @random;
my $pid = 0;
sub octet($self, $max = 256, $length = 32) {
unless ($pid == $$) {
@random = ();
$pid = $$;
}
my $limit = 256 - (256 % $max);
my $octet;
while (1) {
unless (@random) {
my $fh = IO::File->new('/dev/random', 'r');
die $! unless $fh;
my $random;
my $bytes_read = $fh->sysread($random, $length);
die $! unless $bytes_read == $length;
@random = unpack("C$length", $random);
}
$octet = shift @random;
last if $octet < $limit;
}
return $octet % $max;
}
sub string($self, $length, $chars) {
my $string = '';
my $char_count = scalar(@{$chars});
while (length($string) < $length) {
$string .= $chars->[$self->octet($char_count)];
}
return $string;
}
sub alnum($self, $length) {
return $self->string($length, ['0'..'9','a'..'z','A'..'Z']);
}
sub hex($self, $length) {
return $self->string($length, ['0'..'9','a'..'f']);
}
sub num($self, $length) {
return $self->string($length, ['0'..'9']);
}
sub pod($self) {
my $p2t = Pod::Text->new;
my $pod;
$p2t->output_string(\$pod);
$p2t->parse_file(__FILE__);
return $pod;
}
sub source($self) {
my $fh = IO::File->new(__FILE__, 'r');
die $! unless $fh;
return join('', <$fh>);
}
sub main($self, $length) {
print $self->pod;
printf "OUTPUT\n";
printf " alnum: %s\n", $self->alnum($length);
printf " hex: %s\n", $self->hex($length);
printf " num: %s\n", $self->num($length);
}
__PACKAGE__->main(shift(@ARGV) // 8) unless caller();
1;