package Net::OpenID::JanRain::CryptUtil; # vi:ts=4:sw=4 use warnings; use strict; use Carp; use POSIX qw(ceil); use Digest::HMAC_SHA1; use Digest::SHA1; use Math::BigInt lib => 'GMP'; #without GMP Diffie Hellman takes a LONG time use Net::OpenID::JanRain::Util qw( toBase64 fromBase64 ); use Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw( hmacSha1 sha1 numToBytes numToBase64 bytesToNum base64ToNum randomString DEFAULT_DH_MOD DEFAULT_DH_GEN ); use constant { DEFAULT_DH_MOD => "155172898181473697471232257763715539915724801966915404479707795314057629378541917580651227423698188993727816152646631438561595825688188889951272158842675419950341258706556549803580104870537681476726513255747040765857479291291572334510643245094715007229621094194349783925984760375594985848253359305585439638443", DEFAULT_DH_GEN => 2, }; # We need a Cryptographic strength source of randomness # /dev/urandom will work nicely. # output will be biased as 0..255 % len(chrs) # i.e. unbiased if len(chrs) is a power of two sub randomString { my ($length, $chrs) = @_; my $s = ""; if (-e "/dev/urandom") { my ($ur, $randomness, $got); open $ur, "< /dev/urandom"; $got = sysread $ur, $randomness, $length; die "Couldn't get enough of /dev/urandom" unless $got == $length; close $ur; if($chrs) { my @chrs = split(//, $chrs); my @rand = split(//, $randomness); for(my $i=0;$i<$length;$i++) { $s .= $chrs[ord($rand[$i]) % @chrs]; } } else { $s = $randomness; } return $s; } else { # An attack using the predictability of rand is possible. # We strongly recommend using a system with a cryptographically # secure source of randomness to run our OpenID library. warn "No /dev/urandom - YOUR OPENID LIBRARY IS NOT SECURE!"; die "Comment out this line to continue anyway"; my ($length, $chrs) = @_; my $s = ""; if($chrs) { my @chrs = split(//, $chrs); for(1..$length) { $s .= $chrs[int(rand(@chrs))]; } } else { for(1..$length) { $s .= chr(int(rand(256))); } } return($s); } # end randomString } ######################################################################## sub hmacSha1 { my ($key, $text) = @_; return(Digest::HMAC_SHA1::hmac_sha1($text, $key)); } # end hmacSha1 ######################################################################## sub sha1 { my ($s) = @_; return(Digest::SHA1::sha1($s)); } # end sha1 ######################################################################## sub numToBase64 { my ($n) = @_; return toBase64(numToBytes($n)); } ######################################################################## sub numToBytes { my ($n) = @_; if ($n < 0) {die "numToBytes takes only positive integers.";} my @bytes = (); # get a big-endian base 256 representation of n while ($n) { unshift( @bytes, $n % 256 ); $n = $n >> 8; } # first byte high bit is the sign bit if ($bytes[0] > 127) { unshift( @bytes, 0); } my $string = pack('C*',@bytes); return $string; } ######################################################################## sub base64ToNum { my ($b64str) = @_; return bytesToNum(fromBase64($b64str)); } ######################################################################## sub bytesToNum { my ($string) = @_; unless($string) { warn "empty string passed to bytesToNum"; return 0; } my @bytes = unpack('C*',$string); my $n = Math::BigInt->new(0); # high bit set means negative in twos complement; invalid for us return undef if ($bytes[0] > 127); for (@bytes) { $n = $n << 8; $n = $n + $_; } return $n; } ######################################################################## 1;