How to implement Python's os.urandom method in Perl 6?

  • A+
Category:Languages

I'am converting a module writing in Python to Perl 6. In the module, there is a method called create_key, which use os.urandom for cryptographic use:

def create_key(size):     return binascii.hexlify(os.urandom(size))[:16] 

The doc describes os.urandom as:

Return a string of size random bytes suitable for cryptographic use.

In Perl 6, there is a class called Buf, but there is no random method for it. So how to implement os.urandom(size) using Perl 6?

 


You could always just use Python's urandom

sub py-urandom( UInt $size ){   use Inline::Python;    state $py = Inline::Python.new; # reuse the same instance   once $py.import('os');          # load the Python os library only once    $py.call('os','urandom', $size); }  say py-urandom(10)».fmt('%02X').join;  # 1473A7D5455F15D3726B 

To get the above to work required installing the python-dev operating system package. Then installing Inline::Python with zef.


You could use binascii.hexlify as well

sub create-key ( UInt $size ) {   use Inline::Python;    state $py = Inline::Python.new;   once $py.import('os');   once $py.import('binascii');    $py.call('binascii','hexlify', $py.call('os','urandom',$size)).decode('ascii'); } 

I'm sure there is a better way to do the above, but this is the first time I have used Inline::Python. (Which should be obvious because I had to install python-dev to answer this question)


Another way, which may be better in the long-run is to just call getrandom, getentropy, or CryptGenRandom depending on if it is running on Linux, OpenBSD, or Windows. Basically copy the implementation of os.urandom.

Below is a quickly written example.

sub urandom ( UInt $size ){   use NativeCall;    my constant is-win = $*DISTRO.is-win;   my constant is-openbsd = $*DISTRO.name eq 'openbsd';    if is-win {     fail "urandom doesn't handle Windows yet";     # It is more involved on Windows, and I don't use Windows    } elsif is-openbsd {     # note that this is untested as I don't use OpenBSD     if $size > 256 {       fail "urandom doesn't handle more than 256 on OpenBSD"       # note that this could be changed to load it in 256 byte chunks     }      sub getentropy( Buf /buf, size_t /buflen --> int32 ) is native {}      my Buf $buf .= allocate($size);     my $result = getentropy( $buf, $size );      fail if $result !== 0;     $buf    } else { # presumably Linux or other UNIX-like      sub getrandom (Buf /buf, size_t /buflen, uint32 /flags --> ssize_t) is native {}      my Buf $buf .= allocate($size);     my $total = getrandom( $buf, $size, 0 );      fail unless $total == $size; # could be changed to call it for the rest     $buf;   } } 
say urandom(10)».fmt('%02X').join; # 0EF9EDB3EBC724C0E9CE 

If you are on a system with /dev/urandom, you could just read from that instead.

sub urandom ( UInt $size ){   my $urandom will leave {.close}     = '/dev/urandom'.IO.open(:bin,:ro);   $urandom.read( $size ) }  say urandom(10)».fmt('%02X').join; # 01B6C41AD0A77732C328 

The best route would be to use a module that already does the above like Crypt::Random.
It implements the code required for Windows that I didn't, but it uses the /dev/urandom file on *NIX systems.

# alias &Crypt::Random::crypt_random_buf as &urandom my &urandom = do {   use Crypt::Random;   &crypt_random_buf }  say urandom(10)».fmt('%02X').join; # 841720513678B1811E2D 

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: