package Crypt::RSA::Key; use strict; use warnings; ## Crypt::RSA::Keys ## ## Copyright (c) 2001, Vipul Ved Prakash. All rights reserved. ## This code is free software; you can redistribute it and/or modify ## it under the same terms as Perl itself. use base 'Class::Loader'; use base 'Crypt::RSA::Errorhandler'; use Math::Prime::Util qw(random_nbit_prime miller_rabin_random is_frobenius_khashin_pseudoprime); use Crypt::RSA::DataFormat qw(bitsize); use Math::BigInt try => 'GMP, Pari'; use Crypt::RSA::Key::Private; use Crypt::RSA::Key::Public; use Carp; $Crypt::RSA::Key::VERSION = '1.99'; my %MODMAP = ( Native_PKF => { Module => "Crypt::RSA::Key::Public" }, Native_SKF => { Module => "Crypt::RSA::Key::Private" }, SSH_PKF => { Module => "Crypt::RSA::Key::Public::SSH" }, SSH_SKF => { Module => "Crypt::RSA::Key::Private::SSH" }, ); sub new { my $class = shift; my $self = {}; bless $self, $class; $self->_storemap ( %MODMAP ); return $self; } sub generate { my ($self, %params) = @_; my $key; unless ($params{q} && $params{p} && $params{e}) { return $self->error ("Missing argument.") unless $params{Size}; return $self->error ("Keysize too small.") if $params{Size} < 48; return $self->error ("Odd keysize.") if $params{Size} % 2; my $size = int($params{Size}/2); my $verbosity = $params{Verbosity} || 0; # Switch from Maurer prime to nbit prime, then add some more primality # testing. This is faster and gives us a wider set of possible primes. # We really ought to consider the distribution. See: # https://crocs.fi.muni.cz/_media/public/papers/usenixsec16_1mrsakeys_trfimu_201603.pdf # for comments on p/q selection. while (1) { my $p = random_nbit_prime($size); my $q = random_nbit_prime($size); $p = Math::BigInt->new("$p") unless ref($p) eq 'Math::BigInt'; $q = Math::BigInt->new("$q") unless ref($q) eq 'Math::BigInt'; # For unbiased rejection sampling, generate both p/q if size too small. next unless bitsize($p * $q) == $params{Size}; # Verify primes aren't too close together. if ($params{Size} >= 256) { my $threshold = Math::BigInt->new(2)->bpow($params{Size}/2 - 100); my $diff = $p->copy->bsub($q)->babs; next if $diff <= $threshold; } # We could check p-1 and q-1 smoothness. # p and q have passed the strong BPSW test, so it would be shocking # if they were not prime. We'll add a few more tests because they're # cheap and we want to be extra careful, but also don't want to spend # the time doing a full primality proof. do { carp "$p passes BPSW but fails Frobenius test!"; next; } unless is_frobenius_khashin_pseudoprime($p); do { carp "$q passes BPSW but fails Frobenius test!"; next; } unless is_frobenius_khashin_pseudoprime($q); do { carp "$p fails Miller-Rabin testing!"; next; } unless miller_rabin_random($p,3); do { carp "$q fails Miller-Rabin testing!"; next; } unless miller_rabin_random($q,3); $key = { p => $p, q => $q, e => Math::BigInt->new(65537) }; last; } } if ($params{KF}) { $params{PKF} = { Name => "$params{KF}_PKF" }; $params{SKF} = { Name => "$params{KF}_SKF" } } my $pubload = $params{PKF} ? $params{PKF} : { Name => "Native_PKF" }; my $priload = $params{SKF} ? $params{SKF} : { Name => "Native_SKF" }; my $pubkey = $self->_load (%$pubload) || return $self->error ("Couldn't load the public key module: $@"); my $prikey = $self->_load ((%$priload), Args => ['Cipher' => $params{Cipher}, 'Password' => $params{Password} ]) || return $self->error ("Couldn't load the private key module: $@"); $pubkey->Identity ($params{Identity}); $prikey->Identity ($params{Identity}); $pubkey->e ($$key{e} || $params{e}); $prikey->e ($$key{e} || $params{e}); $prikey->p ($$key{p} || $params{p}); $prikey->q ($$key{q} || $params{q}); $prikey->phi ( ($prikey->p - 1) * ($prikey->q - 1) ); $prikey->d ( ($pubkey->e)->copy->bmodinv($prikey->phi) ); $prikey->n ( $prikey->p * $prikey->q ); $pubkey->n ( $prikey->n ); $prikey->dp ($prikey->d % ($prikey->p - 1)); $prikey->dq ($prikey->d % ($prikey->q - 1)); $prikey->u ( ($prikey->p)->copy->bmodinv($prikey->q) ); return $self->error ("d is too small. Regenerate.") if bitsize($prikey->d) < 0.25 * bitsize($prikey->n); $$key{p} = 0; $$key{q} = 0; $$key{e} = 0; if ($params{Filename}) { $pubkey->write (Filename => "$params{Filename}.public"); $prikey->write (Filename => "$params{Filename}.private"); } return ($pubkey, $prikey); } 1; =head1 NAME Crypt::RSA::Key - RSA Key Pair Generator. =head1 SYNOPSIS my $keychain = new Crypt::RSA::Key; my ($public, $private) = $keychain->generate ( Identity => 'Lord Macbeth ', Size => 2048, Password => 'A day so foul & fair', Verbosity => 1, ) or die $keychain->errstr(); =head1 DESCRIPTION This module provides a method to generate an RSA key pair. =head1 METHODS =head2 new() Constructor. =head2 generate() generate() generates an RSA key of specified bitsize. It returns a list of two elements, a Crypt::RSA::Key::Public object that holds the public part of the key pair and a Crypt::RSA::Key::Private object that holds that private part. On failure, it returns undef and sets $self->errstr to appropriate error string. generate() takes a hash argument with the following keys: =over 4 =item B Bitsize of the key to be generated. This should be an even integer > 48. Bitsize is a mandatory argument. =item B String with which the private key will be encrypted. If Password is not provided the key will be stored unencrypted. =item B A string that identifies the owner of the key. This string usually takes the form of a name and an email address. The identity is not bound to the key with a signature. However, a future release or another module will provide this facility. =item B The block cipher which is used for encrypting the private key. Defaults to `Blowfish'. Cipher could be set to any value that works with Crypt::CBC(3) and Tie::EncryptedHash(3). =item B When set to 1, generate() will draw a progress display on STDOUT. =item B The generated key pair will be written to disk, in $Filename.public and $Filename.private files, if this argument is provided. Disk writes can be deferred by skipping this argument and achieved later with the write() method of Crypt::RSA::Key::Public(3) and Crypt::RSA::Key::Private(3). =item B A string that specifies the key format. As of this writing, two key formats, `Native' and `SSH', are supported. KF defaults to `Native'. =item B Secret (Private) Key Format. Instead of specifying KF, the user could choose to specify secret and public key formats separately. The value for SKF can be a string ("Native" or "SSH") or a hash reference that specifies a module name, its constructor and constructor arguments. The specified module is loaded with Class::Loader(3) and must be interface compatible with Crypt::RSA::Key::Private(3). =item B Public Key Format. This option is like SKF but for the public key. =back =head1 ERROR HANDLING See B in Crypt::RSA(3) manpage. =head1 BUGS There's an inefficiency in the way generate() ensures the key pair is exactly Size bits long. This will be fixed in a future release. =head1 AUTHOR Vipul Ved Prakash, Email@vipul.netE =head1 SEE ALSO Crypt::RSA(3), Crypt::RSA::Key::Public(3), Crypt::RSA::Key::Private(3), Tie::EncryptedHash(3), Class::Loader(3), Math::Prime::Util(3) =cut