File Coverage

blib/lib/Crypt/PK/RSA.pm
Criterion Covered Total %
statement 82 119 68.9
branch 47 90 52.2
condition 7 60 11.6
subroutine 16 19 84.2
pod 5 11 45.4
total 157 299 52.5


line stmt bran cond sub pod time code
1             package Crypt::PK::RSA;
2              
3 10     10   583440 use strict;
  10         15  
  10         309  
4 10     10   69 use warnings;
  10         11  
  10         1269  
5             our $VERSION = '0.089_002';
6              
7             require Exporter; our @ISA = qw(Exporter); ### use Exporter 5.57 'import';
8             our %EXPORT_TAGS = ( all => [qw(rsa_encrypt rsa_decrypt rsa_sign_message rsa_verify_message rsa_sign_hash rsa_verify_hash)] );
9             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
10             our @EXPORT = qw();
11              
12 10     10   52 use Carp;
  10         15  
  10         664  
13             $Carp::Internal{(__PACKAGE__)}++;
14 10     10   2925 use CryptX;
  10         17  
  10         335  
15 10     10   2809 use Crypt::Digest qw(digest_data digest_data_b64u);
  10         17  
  10         610  
16 10     10   3052 use Crypt::Misc qw(read_rawfile encode_b64u decode_b64u encode_b64 decode_b64 pem_to_der der_to_pem);
  10         29  
  10         803  
17 10     10   2517 use Crypt::PK;
  10         21  
  10         17105  
18              
19             sub new {
20 116     116 1 1368718 my $self = shift->_new();
21 116 100       64081 return @_ > 0 ? $self->import_key(@_) : $self;
22             }
23              
24             sub export_key_pem {
25 3     3 1 203540 my ($self, $type, $password, $cipher) = @_;
26 3         16 local $SIG{__DIE__} = \&CryptX::_croak;
27             # public_x509 uses the same DER as public, just different PEM header
28 3 100 50     13 my $der_type = ($type || '') eq 'public_x509' ? 'public' : ($type || '');
      50        
29 3         151 my $key = $self->export_key_der($der_type);
30 3 50       5 return unless $key;
31              
32             # PKCS#1 RSAPrivateKey** (PEM header: BEGIN RSA PRIVATE KEY)
33             # PKCS#8 PrivateKeyInfo* (PEM header: BEGIN PRIVATE KEY)
34             # PKCS#8 EncryptedPrivateKeyInfo** (PEM header: BEGIN ENCRYPTED PRIVATE KEY)
35 3 100       11 return der_to_pem($key, "RSA PRIVATE KEY", $password, $cipher) if $type eq 'private';
36              
37             # PKCS#1 RSAPublicKey* (PEM header: BEGIN RSA PUBLIC KEY)
38 2 100       7 return der_to_pem($key, "RSA PUBLIC KEY") if $type eq 'public';
39             # X.509 SubjectPublicKeyInfo** (PEM header: BEGIN PUBLIC KEY)
40 1 50       4 return der_to_pem($key, "PUBLIC KEY") if $type eq 'public_x509';
41             }
42              
43             sub export_key_jwk {
44 0     0 1 0 my ($self, $type, $wanthash) = @_;
45 0         0 local $SIG{__DIE__} = \&CryptX::_croak;
46 0         0 my $kh = $self->key2hash;
47 0 0       0 if ($type eq 'private') {
    0          
48 0 0 0     0 return unless $kh->{N} && $kh->{e} && $kh->{d} && $kh->{p} && $kh->{q} && $kh->{dP} && $kh->{dQ} && $kh->{qP};
      0        
      0        
      0        
      0        
      0        
      0        
49 0         0 for (qw/N e d p q dP dQ qP/) {
50 0 0       0 $kh->{$_} = "0$kh->{$_}" if length($kh->{$_}) % 2;
51             }
52             my $hash = {
53             kty => "RSA",
54             n => encode_b64u(pack("H*", $kh->{N})),
55             e => encode_b64u(pack("H*", $kh->{e})),
56             d => encode_b64u(pack("H*", $kh->{d})),
57             p => encode_b64u(pack("H*", $kh->{p})),
58             q => encode_b64u(pack("H*", $kh->{q})),
59             dp => encode_b64u(pack("H*", $kh->{dP})),
60             dq => encode_b64u(pack("H*", $kh->{dQ})),
61 0         0 qi => encode_b64u(pack("H*", $kh->{qP})),
62             };
63 0 0       0 return $wanthash ? $hash : CryptX::_encode_json($hash);
64             }
65             elsif ($type eq 'public') {
66 0 0 0     0 return unless $kh->{N} && $kh->{e};
67 0         0 for (qw/N e/) {
68 0 0       0 $kh->{$_} = "0$kh->{$_}" if length($kh->{$_}) % 2;
69             }
70             my $hash = {
71             kty => "RSA",
72             n => encode_b64u(pack("H*", $kh->{N})),
73 0         0 e => encode_b64u(pack("H*", $kh->{e})),
74             };
75 0 0       0 return $wanthash ? $hash : CryptX::_encode_json($hash);
76             }
77             }
78              
79             sub export_key_jwk_thumbprint {
80 0     0 1 0 my ($self, $hash_name) = @_;
81 0         0 local $SIG{__DIE__} = \&CryptX::_croak;
82 0   0     0 $hash_name ||= 'SHA256';
83 0         0 my $h = $self->export_key_jwk('public', 1);
84 0         0 my $json = CryptX::_encode_json({kty=>$h->{kty}, n=>$h->{n}, e=>$h->{e}});
85 0         0 return digest_data_b64u($hash_name, $json);
86             }
87              
88             sub import_key {
89 176     176 1 218187 my ($self, $key, $password) = @_;
90 176         971 local $SIG{__DIE__} = \&CryptX::_croak;
91 176 50       542 croak "FATAL: undefined key" unless $key;
92              
93             # special case
94 176 100       486 if (ref($key) eq 'HASH') {
95 3 50 33     15 if ($key->{N} && $key->{e}) {
96             # hash exported via key2hash
97 3         666 return $self->_import_hex($key->{N}, $key->{e}, $key->{d}, $key->{p}, $key->{q}, $key->{dP}, $key->{dQ}, $key->{qP});
98             }
99 0 0 0     0 if ($key->{n} && $key->{e} && $key->{kty} && $key->{kty} eq "RSA") {
      0        
      0        
100 0         0 $key = {%$key}; #make a copy so that the modifications below stay local
101              
102             # hash with items corresponding to JSON Web Key (JWK)
103 0         0 for (qw/n e d p q dp dq qi/) {
104 0 0       0 $key->{$_} = eval { unpack("H*", decode_b64u($key->{$_})) } if exists $key->{$_};
  0         0  
105             }
106 0         0 return $self->_import_hex($key->{n}, $key->{e}, $key->{d}, $key->{p}, $key->{q}, $key->{dp}, $key->{dq}, $key->{qi});
107             }
108 0         0 croak "FATAL: unexpected RSA key hash";
109             }
110              
111 173         235 my $data;
112 173 100       3097 if (ref($key) eq 'SCALAR') {
    50          
113 72         171 $data = $$key;
114             }
115             elsif (-f $key) {
116 101         355 $data = read_rawfile($key);
117             }
118             else {
119 0         0 croak "FATAL: non-existing file '$key'";
120             }
121 173 50       479 croak "FATAL: invalid key data" unless $data;
122              
123 173 100       1922 if ($data =~ /-----BEGIN (RSA PUBLIC|RSA PRIVATE|PUBLIC|PRIVATE|ENCRYPTED PRIVATE) KEY-----(.+?)-----END (RSA PUBLIC|RSA PRIVATE|PUBLIC|PRIVATE|ENCRYPTED PRIVATE) KEY-----/s) {
    100          
    100          
    100          
    50          
    100          
124 55         44832 return $self->_import_pem($data, $password);
125             }
126             elsif ($data =~ /-----BEGIN CERTIFICATE-----(.+?)-----END CERTIFICATE-----/s) {
127 1         102 return $self->_import_pem($data, undef);
128             }
129             elsif ($data =~ /-----BEGIN OPENSSH PRIVATE KEY-----(.+?)-----END OPENSSH PRIVATE KEY-----/s) {
130 12         1264917 return $self->_import_openssh($data, $password);
131             }
132             elsif ($data =~ /---- BEGIN SSH2 PUBLIC KEY ----(.+?)---- END SSH2 PUBLIC KEY ----/s) {
133 6         482 return $self->_import_openssh($data, undef);
134             }
135             elsif ($data =~ /^\s*(\{.*?\})\s*$/s) {
136             # JSON Web Key (JWK) - https://www.rfc-editor.org/rfc/rfc7517
137 0         0 my $json = "$1";
138 0         0 my $h = CryptX::_decode_json($json);
139 0 0 0     0 if ($h && $h->{kty} eq "RSA") {
140 0         0 for (qw/n e d p q dp dq qi/) {
141 0 0       0 $h->{$_} = eval { unpack("H*", decode_b64u($h->{$_})) } if exists $h->{$_};
  0         0  
142             }
143 0 0 0     0 return $self->_import_hex($h->{n}, $h->{e}, $h->{d}, $h->{p}, $h->{q}, $h->{dp}, $h->{dq}, $h->{qi}) if $h->{n} && $h->{e};
144             }
145             }
146             elsif ($data =~ /ssh-rsa\s+(\S+)/) {
147 6         82 $data = decode_b64("$1");
148 6         43 my ($typ, $e, $N) = Crypt::PK::_ssh_parse($data);
149 6 50 33     1500 return $self->_import_hex(unpack("H*", $N), unpack("H*", $e)) if $typ && $e && $N && $typ eq 'ssh-rsa';
      33        
      33        
150             }
151             else {
152             # DER format
153 93   33     150 my $rv = eval { $self->_import($data) } || eval { $self->_import_pkcs8($data, $password) } || eval { $self->_import_x509($data) };
154 93 50       801 return $rv if $rv;
155             }
156              
157 0         0 croak "FATAL: invalid or unsupported RSA key format";
158             }
159              
160             ### FUNCTIONS
161              
162             sub rsa_encrypt { # legacy/obsolete
163 2     2 0 1469290 my $key = shift;
164 2         11 local $SIG{__DIE__} = \&CryptX::_croak;
165 2 100       11 $key = __PACKAGE__->new($key) unless ref $key;
166 2 100       215 croak "FATAL: invalid 'key' param" unless ref($key) eq __PACKAGE__;
167 1         222 return $key->encrypt(@_);
168             }
169              
170             sub rsa_decrypt { # legacy/obsolete
171 1     1 0 282 my $key = shift;
172 1         4 local $SIG{__DIE__} = \&CryptX::_croak;
173 1 50       9 $key = __PACKAGE__->new($key) unless ref $key;
174 1 50       6 croak "FATAL: invalid 'key' param" unless ref($key) eq __PACKAGE__;
175 1         5684 return $key->decrypt(@_);
176             }
177              
178             sub rsa_sign_hash { # legacy/obsolete
179 1     1 0 256 my $key = shift;
180 1         5 local $SIG{__DIE__} = \&CryptX::_croak;
181 1 50       9 $key = __PACKAGE__->new($key) unless ref $key;
182 1 50       7 croak "FATAL: invalid 'key' param" unless ref($key) eq __PACKAGE__;
183 1         5870 return $key->sign_hash(@_);
184             }
185              
186             sub rsa_verify_hash { # legacy/obsolete
187 1     1 0 280 my $key = shift;
188 1         5 local $SIG{__DIE__} = \&CryptX::_croak;
189 1 50       7 $key = __PACKAGE__->new($key) unless ref $key;
190 1 50       4 croak "FATAL: invalid 'key' param" unless ref($key) eq __PACKAGE__;
191 1         241 return $key->verify_hash(@_);
192             }
193              
194             sub rsa_sign_message { # legacy/obsolete
195 2     2 0 943 my $key = shift;
196 2         11 local $SIG{__DIE__} = \&CryptX::_croak;
197 2 100       15 $key = __PACKAGE__->new($key) unless ref $key;
198 2 100       105 croak "FATAL: invalid 'key' param" unless ref($key) eq __PACKAGE__;
199 1         5775 return $key->sign_message(@_);
200             }
201              
202             sub rsa_verify_message { # legacy/obsolete
203 1     1 0 278 my $key = shift;
204 1         5 local $SIG{__DIE__} = \&CryptX::_croak;
205 1 50       8 $key = __PACKAGE__->new($key) unless ref $key;
206 1 50       6 croak "FATAL: invalid 'key' param" unless ref($key) eq __PACKAGE__;
207 1         264 return $key->verify_message(@_);
208             }
209              
210 0     0     sub CLONE_SKIP { 1 } # prevent cloning
211              
212             1;
213              
214             =pod
215              
216             =head1 NAME
217              
218             Crypt::PK::RSA - Public key cryptography based on RSA
219              
220             =head1 SYNOPSIS
221              
222             ### OO interface
223              
224             my $message = 'hello world';
225             my $private_key = Crypt::PK::RSA->new();
226             $private_key->generate_key(256, 65537);
227              
228             my $public_der = $private_key->export_key_der('public');
229             my $public_key = Crypt::PK::RSA->new(\$public_der);
230              
231             my $ciphertext = $public_key->encrypt($message);
232             my $plaintext = $private_key->decrypt($ciphertext);
233              
234             my $signature = $private_key->sign_message($message, 'SHA256');
235             $public_key->verify_message($signature, $message, 'SHA256') or die "ERROR";
236              
237             my $private_der = $private_key->export_key_der('private');
238             my $private_pem = $private_key->export_key_pem('private');
239             my $public_pem = $public_key->export_key_pem('public');
240              
241             =head1 DESCRIPTION
242              
243             The module provides a full featured RSA implementation.
244              
245             Legacy function-style wrappers still exist in code for backwards compatibility,
246             but they are intentionally undocumented.
247              
248             =head1 METHODS
249              
250             =head2 new
251              
252             my $source = Crypt::PK::RSA->new();
253             $source->generate_key(256, 65537);
254              
255             my $public_der = $source->export_key_der('public');
256             my $pub = Crypt::PK::RSA->new(\$public_der);
257              
258             my $private_pem = $source->export_key_pem('private', 'secret', 'AES-256-CBC');
259             my $priv = Crypt::PK::RSA->new(\$private_pem, 'secret');
260              
261             Passing C<$filename> or C<\$buffer> to C is equivalent: both forms
262             immediately import the key material into the new object.
263              
264             =head2 generate_key
265              
266             Uses the bundled C PRNG via libtomcrypt's C.
267             Returns the object itself (for chaining).
268              
269             $pk->generate_key($size, $e);
270             # $size .. [integer] key size: 128-512 bytes (DEFAULT is 256)
271             # $e ..... [integer] exponent: 3, 17, 257 or 65537 (DEFAULT is 65537)
272              
273             The C<$size> and C<$e> arguments use Perl's usual numeric-to-integer coercion
274             before reaching the XS layer. Pass exact integers; values like C<10.9> or
275             C<"1e2"> are coerced rather than rejected.
276              
277             =head2 import_key
278              
279             Loads private or public key in DER or PEM format.
280              
281             my $source = Crypt::PK::RSA->new();
282             $source->generate_key(256, 65537);
283              
284             my $public_der = $source->export_key_der('public');
285             my $pub = Crypt::PK::RSA->new();
286             $pub->import_key(\$public_der);
287              
288             my $private_pem = $source->export_key_pem('private', 'secret', 'AES-256-CBC');
289             my $priv = Crypt::PK::RSA->new();
290             $priv->import_key(\$private_pem, 'secret');
291              
292             The same method also accepts filenames instead of buffers.
293              
294             Loading private or public keys from a Perl HASH:
295              
296             $pk->import_key($hashref);
297              
298             # the $hashref is either a key exported via key2hash
299             $pk->import_key({
300             e => "10001", #public exponent
301             d => "9ED5C3D3F866E06957CA0E9478A273C39BBDA4EEAC5B...", #private exponent
302             N => "D0A5CCCAE03DF9C2F5C4C8C0CE840D62CDE279990DC6...", #modulus
303             p => "D3EF0028FFAB508E2773C659E428A80FB0E9211346B4...", #p factor of N
304             q => "FC07E46B163CAB6A83B8E467D169534B2077DCDEECAE...", #q factor of N
305             qP => "88C6D406F833DF73C8B734548E0385261AD51F4187CF...", #1/q mod p CRT param
306             dP => "486F142FEF0A1F53269AC43D2EE4D263E2841B60DA36...", #d mod (p - 1) CRT param
307             dQ => "4597284B2968B72C4212DB7E8F24360B987B80514DA9...", #d mod (q - 1) CRT param
308             });
309              
310             # or a hash with items corresponding to JWK (JSON Web Key)
311             $pk->import_key({
312             kty => "RSA",
313             n => "0vx7agoebGcQSuuPiLJXZpt...eZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
314             e => "AQAB",
315             d => "X4cTteJY_gn4FYPsXB8rdXi...FLN5EEaG6RoVH-HLKD9Mdx5ooGURknhnrRwUkC7h5fJLMWbFAKLWY2v7B6NqSzUvx0_YSf",
316             p => "83i-7IvMGXoMXCskv73TKr8...Z27zvoj6pbUQyLPBQxtPnwD20-60eTmD2ujMt5PoMrm8RmNhVWtjjMmMjOpSicFHjXOuVI",
317             q => "3dfOR9cuYq-0S-mkFLzgItg...q3hWeMuG0ouqnb3obLyuqjVZQ1dIrdgTnCdYzBcOW5r37AFXjift_NGiovonzhKpoVVS78",
318             dp => "G4sPXkc6Ya9y8oJW9_ILj4...zi_H7TkS8x5SdX3oE0oiYwxIiemTAu0UOa5pgFGyJ4c8t2VF40XRugKTP8akhFo5tA77Qe",
319             dq => "s9lAH9fggBsoFR8Oac2R_E...T2kGOhvIllTE1efA6huUvMfBcpn8lqW6vzzYY5SSF7pMd_agI3G8IbpBUb0JiraRNUfLhc",
320             qi => "GyM_p6JrXySiz1toFgKbWV...4ypu9bMWx3QJBfm0FoYzUIZEVEcOqwmRN81oDAaaBk0KWGDjJHDdDmFW3AN7I-pux_mHZG",
321             });
322              
323             Supported key formats:
324              
325             # all formats can be loaded from a file
326             my $pk = Crypt::PK::RSA->new($filename);
327              
328             # or from a buffer containing the key
329             my $pk = Crypt::PK::RSA->new(\$buffer_with_key);
330              
331             =over
332              
333             =item * RSA public keys
334              
335             -----BEGIN PUBLIC KEY-----
336             MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHlYKg9DeHB3/dY1D9WCyJTnl5
337             vEzAXpUOL9tDtdPUl96brIbbdMLooO1hKjsq98kLs1q4vOn/pxvzk0BRwhiu7Vvb
338             VUjAn/2HHDDL0U1utqqlMJhaffeLI3HEq5o/lSMFY7sSkZU/E4YX1yqAN0SE7xfK
339             B2uzcNq60sMIfp6siQIDAQAB
340             -----END PUBLIC KEY-----
341              
342             =item * RSA private keys
343              
344             -----BEGIN RSA PRIVATE KEY-----
345             MIICXQIBAAKBgQDHlYKg9DeHB3/dY1D9WCyJTnl5vEzAXpUOL9tDtdPUl96brIbb
346             dMLooO1hKjsq98kLs1q4vOn/pxvzk0BRwhiu7VvbVUjAn/2HHDDL0U1utqqlMJha
347             ffeLI3HEq5o/lSMFY7sSkZU/E4YX1yqAN0SE7xfKB2uzcNq60sMIfp6siQIDAQAB
348             AoGBAI5+GgNcGQDYw9uF+t7FwxZM5sGZRJrbbEPyuvL+sDxKKW6voKCyHi4EJzaF
349             9jRZMDqgVJcsmUwjPPuMGBHHJ+MI5Zb3L0jbZkyx8u+U5gf88oy9eZmfGOjmHcMB
350             oCgzyoLmJETuyADg2onLanuY3jggFb3tq/jimKjO8xM2R6zhAkEA7uXWWyJI9cCN
351             zrVt5R5v6oosjZ4r5VILGMqBRLrzfTvH+WDMK6Rl/2MHE+YDeLajzunaM8qY2456
352             GTYEXQsIdQJBANXfMEtXocSdPtoVj3ME8Do/0r+ApgTdcDPCwXOzkmkEJW/UFMSn
353             b8CYF5G6sZQN9L5z3s2nvi55PaFV8Q0LMUUCQBh9GvIQm6YFbQPpeTBpZFOIgnSp
354             6BoDxPtvlryy5U7LF/6qO4OlwIbjYdBaXbS8FCKbujBg7jZjboSzEtNu1BkCQDGT
355             w0Yz0jQZn3A+fzpScr2N/fSWheWqz0+wXdfMUKw3YdZCe236wlUK7KvDc1a2xX1A
356             ru1NbTCoujikC3TSm2ECQQDKQshchJlZJmFv9vCFQlGCA/EX+4406xvOOiixbPYC
357             pIB4Ee2cmvEdAqSaOjrvgs5zvaCCFBO0MecPStCAxUX6
358             -----END RSA PRIVATE KEY-----
359              
360             =item * RSA private keys in password protected PEM format
361              
362             -----BEGIN RSA PRIVATE KEY-----
363             Proc-Type: 4,ENCRYPTED
364             DEK-Info: DES-EDE3-CBC,4D697440FF5AEF18
365              
366             C09H49Gn99o8b8O2r4+Hqao4r3udvC+QSSfsk20sXatyuZSEmbhyqKAB+13NRj+3
367             KIsRTqnL9VkeibIGgLHuekOFKAqeSVZ0PmR4bGWEFxUPAYUvg9N9pIa6hGtNZG+y
368             TEpOAfFITb1pbHQhp3j8y7qmKc5kY5LrZSFE8WwA24NTG773E07wJgRxKDkXNGOl
369             kki6oYArNEps0DdtHFxzgdRg0+yaotXuFJRuC5V4YzKGG/oSRcgYyXKTwCndb3xt
370             aHgI2WprQAPg+qOpLABzoi7bEjCqbHWrwkvnAngylbim2Uyvw1e1xKnzlgIHU7pv
371             e/J+s00pTItfqW1IpY2mh4C9nkfkfVKBKaAv7jO0s6aPySATqsdlrzv2kpF6Ub4J
372             kgaZDOfZ4K3qkyAYVLWcQeDqg4glv9Ah2J05bTm4qrIMmthYnThyQlGvcjUfCMXs
373             0t+mEQbsRY7xKt0o6HzzvQlJ+JsFlLORoslAubJX9iLqpEdnlrj1lD9bo6uIClZ5
374             5+aoLcAyz1D4OsauuP5i8VFu+Is+QG4SN/vHVuArjkqi3VpLwSAjNDY+KWbq042l
375             CqlM2mwm6FIGUZQFxiLHJD7WDmk1xmae++m+XG9CEDTfrUQ5v+l0O6BTrl80XUfU
376             w3gzAWbSjz3UK0FpKeABVFPE9fjNP9fTcS6qL5YJWBPflwxCAbVgsBOW4bOMpDGK
377             BJDQTeShWn4BlYCe/vgThI9ERdgZhRz4NcFeDgVA/CqQzVqptvz4PSqH46fqUN2n
378             4PtJgKE5cASYUBuAjlD71FecSVVM/OTzL1uxYzXBilzvVn2vSHgo9g==
379             -----END RSA PRIVATE KEY-----
380              
381             =item * PKCS#8 encoded private keys
382              
383             -----BEGIN PRIVATE KEY-----
384             MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBANPN17xW4EkH5PXG
385             1i/i3rE1EXFcCHyxmz95VRBDs1p3MuYf9mxntbfYAmuzS3KrRWh3IyX/Eh80N/v9
386             OXPlwZbVqSTX+L3pCEJtRtsWn0zmswGThjMZiwle0oWuap63L35F1QN8EDaSPSBC
387             yGELNRr6rwVYq0w5b+LOcaCZ+/H1AgMBAAECgYEApfu3aGpww+rC3HUhX0+ckyTy
388             cXLdV9LbxidwqRlVEb0+DyfXNucjelp2sy5EHy3na9GJovo8mmWSxhCRGKliRkQ6
389             XgrEMZdCSaWI2AazuHAGlUJRFEVkvdla3AuBAn6y0YdDp/3kbg0yahmKyD8Gq74z
390             nUYbDL3R5JtR2Ad/KlUCQQDvSEICTHbO/BF7hVmlKRYZSNHKEPrv8X/OlppS14Kv
391             QRwc+CZ5+l6T1Y+l5cHJQUXrXZoWS1K741TXdUhjjUd7AkEA4pod804Ex8sttdWi
392             pHMfeyj+IbPAk5XnBc91jT7AYIeL8ccjtfl99xhMsGFaxrh3wA/4SGEvwzWkbxcq
393             H8G5TwJAKNG+0P2SVwURRm0dOdukdXPCtiHnbP9Zujhe4zr4hEUrMpXymmRntfh8
394             pORpBpgoAVraams3Fe5WDttnGfSD+QJAOOC6V9HjfUrQhG3FT0XeRwm5EDiQQ/tC
395             a8DxHqz7mL8tL1ju68ReC+G7jiJBqNOwqzLW/UP3uyYByiikWChGHQJAHUau7jIM
396             45ErO096n94Vh95p76ANxOroWszOt39TyvJOykIfoPwFagLrBWV9Jjos2/D54KE+
397             fyoy4t3yHT+/nw==
398             -----END PRIVATE KEY-----
399              
400             =item * PKCS#8 encrypted private keys - password protected keys (supported since: CryptX-0.062)
401              
402             -----BEGIN ENCRYPTED PRIVATE KEY-----
403             MIICojAcBgoqhkiG9w0BDAEDMA4ECCQk+Rr1yzzcAgIIAASCAoD/mgpUFjxxM/Ty
404             Yt+NeT0Fo4echgoGksqs6+rYhO16oshG664emZfkuNoFGGzJ38X6GVuqIXhlPnYQ
405             biKvL37dN/KnoGytFHq9Wnk8dDwjGHPtwajhW5WuIV3NuhW/AO1PF/cRZKFjWrPt
406             NWY5CrpfH6t6zojoe+5uyXpH29lQy4OqvSRdPIt/12UcB+tzV7XzSWEuXh8HAi8a
407             sYUu6tuCFnq4GrD2ffM4KWFmL5GqBAwN6m0KkyrNni9XT+RaA6zEhv/lVcwg2esa
408             4/EzRs0ixzzZDKaml8oCMl9RHtFAbQmdlfV7Ip4rGK9BwY6UFiDMIVru6HynOVQK
409             vvZ+j//bgO+3ubrv7psX+vC9Fy/MoH2Tc7MIwDN/QVTciPZlzjWBnBNxMfeFKtEn
410             d7NFiapgfLuRQIiDTMrW/clcqvO54NphxhrcgUEoxos4twKZARntqPZHtf8nEM2x
411             2sEF5kI65aEF/5Yy16qvP0vZAA2B1kcIdXZ8XLZCp4c3olhkIrmgUpo1gyFXdCoC
412             7dT5Cz7/YLkq5hkcFrtp4V9BZMR24fSttc4p24N5xuZ+JneGnGkLX6B+nJAtm9vw
413             bZA6P+23GI0qeMzL3HJXwCOTSsWfm/H9W5+2Zmw851aAmE+pZLni/pk3e3iNSWgs
414             946x/doA5O0uCFsU7oxme+WAIp2SjhxGoe808Lf1CCFMPboFi1O/E0NsX8SIEX+i
415             U+UHi4kxZqVkr3Q5SB/9kiSv8K1bE787yueQOT/dsTYYaMsjAbkEZo0o/47F32T6
416             A2ioXHOV/pr5zNHqE5tL+qKEcLYbAUF1O+WvmdqYz+vHQjRQBatAqTmncvLDYr/j
417             1HPwZX2d
418             -----END ENCRYPTED PRIVATE KEY-----
419              
420             =item * RSA public key from X509 certificate
421              
422             -----BEGIN CERTIFICATE-----
423             MIIC8zCCAdugAwIBAgIJAPi+LvMU3uGWMA0GCSqGSIb3DQEBCwUAMBAxDjAMBgNV
424             BAMMBXBva3VzMB4XDTE3MDcxNDE0MTAyMFoXDTIwMDQwOTE0MTAyMFowEDEOMAwG
425             A1UEAwwFcG9rdXMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCQima
426             SUIMIdz5uVevzcScbcj06xs1OLaFKUoPJ8v+xP6Ut61BQhAvc8GYuw2uRx223hZC
427             r3HYLfSdWIfmOIAtlL8cPYPVoSivJtpSGE6fBG1tlBjVgXWRmJGR/oxx6Y5QDwcB
428             Q4GZKga8TtHQoY5idZuatYOFZGfMIcIUC0Uoda+YSypnw7A90F/JvlpcTUh3Fnem
429             VinqEA6XOegU9dCZk/29sXqauBjbdGihh8DvpklOhY16eQoiR3909AywQ0KUmI+R
430             Sa9E8oIsmUDetFuXEvana+sD3y42tU+cd2nhBPRETbSXPcum0B3uF4yKgweuJy5D
431             cvtVQIFVkkh4+AWNAgMBAAGjUDBOMB0GA1UdDgQWBBSS6V5PVGyN92NoB0AVLcOb
432             pzR3SzAfBgNVHSMEGDAWgBSS6V5PVGyN92NoB0AVLcObpzR3SzAMBgNVHRMEBTAD
433             AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBIszrBjoJ39axsS6Btbvwvo8vAmgiSWsav
434             7AmjXOAwknHPaCcDmrdOys5POD0DNRwNeRsnxFiZ/UL8Vmj2JGDLgAw+/v32MwfX
435             Ig7m+oIbO8KqDzlYvS5kd3suJ5C21hHy1/JUtfofZLovZH7ZRzhTAoRvCYaodW90
436             2o8ZqmyCdcXPzjFmoJ2xYzs/Sf8/E1cHfb+4HjOpeRnKxDvG0gwWzcsXpUrw2pNO
437             Oztj6Rd0THNrf/anIeYVtAHX4aqZA8Kbv2TyJd+9g78usFw1cn+8vfmilm6Pn0DQ
438             a+I5GyGd7BJI8wYuWqIStzvrJHbQQaNrSk7hgjWYiYlcsPh6w2QP
439             -----END CERTIFICATE-----
440              
441             =item * SSH public RSA keys
442              
443             ssh-rsa AAAAB3NzaC1yc2EAAAADAQA...6mdYs5iJNGu/ltUdc=
444              
445             =item * SSH public RSA keys (RFC-4716 format)
446              
447             ---- BEGIN SSH2 PUBLIC KEY ----
448             Comment: "768-bit RSA, converted from OpenSSH"
449             AAAAB3NzaC1yc2EAAAADAQABAAAAYQDYebeGQFCnlQiNRE7r9UEbjr+DQMTdw1ZHGB2w6x
450             D/DzKem8761GdCpqsLrGaw2D7aSIoP1B5Sz870YoVWHn6Ao7Hvm17V3Kxfn4B01GNQTM5+
451             L26mdYs5iJNGu/ltUdc=
452             ---- END SSH2 PUBLIC KEY ----
453              
454             =item * RSA private keys in JSON Web Key (JWK) format
455              
456             See L
457              
458             {
459             "kty":"RSA",
460             "n":"0vx7agoebGcQSuuPiLJXZpt...eZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
461             "e":"AQAB",
462             "d":"X4cTteJY_gn4FYPsXB8rdXi...FLN5EEaG6RoVH-HLKD9Mdx5ooGURknhnrRwUkC7h5fJLMWbFAKLWY2v7B6NqSzUvx0_YSf",
463             "p":"83i-7IvMGXoMXCskv73TKr8...Z27zvoj6pbUQyLPBQxtPnwD20-60eTmD2ujMt5PoMrm8RmNhVWtjjMmMjOpSicFHjXOuVI",
464             "q":"3dfOR9cuYq-0S-mkFLzgItg...q3hWeMuG0ouqnb3obLyuqjVZQ1dIrdgTnCdYzBcOW5r37AFXjift_NGiovonzhKpoVVS78",
465             "dp":"G4sPXkc6Ya9y8oJW9_ILj4...zi_H7TkS8x5SdX3oE0oiYwxIiemTAu0UOa5pgFGyJ4c8t2VF40XRugKTP8akhFo5tA77Qe",
466             "dq":"s9lAH9fggBsoFR8Oac2R_E...T2kGOhvIllTE1efA6huUvMfBcpn8lqW6vzzYY5SSF7pMd_agI3G8IbpBUb0JiraRNUfLhc",
467             "qi":"GyM_p6JrXySiz1toFgKbWV...4ypu9bMWx3QJBfm0FoYzUIZEVEcOqwmRN81oDAaaBk0KWGDjJHDdDmFW3AN7I-pux_mHZG",
468             }
469              
470             B For JWK support you need to have L module installed.
471              
472             =item * RSA public keys in JSON Web Key (JWK) format
473              
474             {
475             "kty":"RSA",
476             "n": "0vx7agoebGcQSuuPiLJXZp...tN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECP",
477             "e":"AQAB",
478             }
479              
480             B For JWK support you need to have L module installed.
481              
482             =back
483              
484             =head2 export_key_der
485              
486             Returns the key as a binary DER-encoded string.
487              
488             my $private_der = $pk->export_key_der('private');
489             #or
490             my $public_der = $pk->export_key_der('public');
491              
492             =head2 export_key_pem
493              
494             Returns the key as a PEM-encoded string (ASCII).
495              
496             my $private_pem = $pk->export_key_pem('private');
497             #or
498             my $public_pem = $pk->export_key_pem('public');
499             #or
500             my $public_pem = $pk->export_key_pem('public_x509');
501              
502             With parameter C<'public'> uses header and footer lines:
503              
504             -----BEGIN RSA PUBLIC KEY------
505             -----END RSA PUBLIC KEY------
506              
507             With parameter C<'public_x509'> uses header and footer lines:
508              
509             -----BEGIN PUBLIC KEY------
510             -----END PUBLIC KEY------
511              
512             Support for password protected PEM keys
513              
514             my $private_pem = $pk->export_key_pem('private', $password);
515             #or
516             my $private_pem = $pk->export_key_pem('private', $password, $cipher);
517              
518             # supported ciphers: 'DES-CBC'
519             # 'DES-EDE3-CBC'
520             # 'SEED-CBC'
521             # 'CAMELLIA-128-CBC'
522             # 'CAMELLIA-192-CBC'
523             # 'CAMELLIA-256-CBC'
524             # 'AES-128-CBC'
525             # 'AES-192-CBC'
526             # 'AES-256-CBC' (DEFAULT)
527              
528             =head2 export_key_jwk
529              
530             I
531              
532             Returns a JSON string, or a hashref if the optional second argument is true.
533              
534             Exports public/private keys as a JSON Web Key (JWK).
535              
536             my $private_json_text = $pk->export_key_jwk('private');
537             #or
538             my $public_json_text = $pk->export_key_jwk('public');
539              
540             Also exports public/private keys as a Perl HASH with JWK structure.
541              
542             my $jwk_hash = $pk->export_key_jwk('private', 1);
543             #or
544             my $jwk_hash = $pk->export_key_jwk('public', 1);
545              
546             B For JWK support you need to have L module installed.
547              
548             =head2 export_key_jwk_thumbprint
549              
550             I
551              
552             Exports the key's JSON Web Key Thumbprint as a string.
553              
554             If you don't know what this is, see RFC 7638 L.
555              
556             my $thumbprint = $pk->export_key_jwk_thumbprint('SHA256');
557              
558             =head2 encrypt
559              
560             Returns the ciphertext as a binary string.
561              
562             my $pk = Crypt::PK::RSA->new($pub_key_filename);
563             my $ct = $pk->encrypt($message);
564             #or
565             my $ct = $pk->encrypt($message, $padding);
566             #or
567             my $ct = $pk->encrypt($message, 'oaep', $hash_name, $lparam);
568              
569             # $padding .................... [string] 'oaep' (DEFAULT), 'v1.5' or 'none' (INSECURE)
570             # $hash_name (only for oaep) .. [string] 'SHA1' (DEFAULT), 'SHA256' or any other hash supported by Crypt::Digest
571             # $lparam (only for oaep) ..... [binary string] DEFAULT is empty string
572              
573             =head2 decrypt
574              
575             Returns the plaintext as a binary string.
576              
577             my $pk = Crypt::PK::RSA->new($priv_key_filename);
578             my $pt = $pk->decrypt($ciphertext);
579             #or
580             my $pt = $pk->decrypt($ciphertext, $padding);
581             #or
582             my $pt = $pk->decrypt($ciphertext, 'oaep', $hash_name, $lparam);
583              
584             # $padding .................... [string] 'oaep' (DEFAULT), 'v1.5' or 'none' (INSECURE)
585             # $hash_name (only for oaep) .. [string] 'SHA1' (DEFAULT), 'SHA256' or any other hash supported by Crypt::Digest
586             # $lparam (only for oaep) ..... [binary string] DEFAULT is empty string
587              
588             =head2 sign_message
589              
590             Returns the signature as a binary string.
591              
592             my $pk = Crypt::PK::RSA->new($priv_key_filename);
593             my $signature = $pk->sign_message($message);
594             #or
595             my $signature = $pk->sign_message($message, $hash_name);
596             #or
597             my $signature = $pk->sign_message($message, $hash_name, $padding);
598             #or
599             my $signature = $pk->sign_message($message, $hash_name, 'pss', $saltlen);
600              
601             # $hash_name ............... [string] 'SHA1' (DEFAULT, INSECURE), 'SHA256' or any other hash supported by Crypt::Digest
602             # $padding ................. [string] 'pss' (DEFAULT) or 'v1.5' or 'none' (INSECURE)
603             # $saltlen (only for pss) .. [integer] DEFAULT is 12
604              
605             B The C<$hash_name> default is C<'SHA1'> only for backward compatibility.
606             SHA-1 is vulnerable to practical collision attacks and is not safe for signing
607             messages whose content may be influenced by an attacker. Always pass an explicit
608             hash name such as C<'SHA256'>. The same applies to L,
609             L and L.
610              
611             For RSASSA-PSS with SHAKE per L
612             use C<'SHAKE128'> (output 32 bytes, C<$saltlen> = 32) or C<'SHAKE256'> (output
613             64 bytes, C<$saltlen> = 64); both the message hash and the internal MGF use
614             SHAKE directly as defined by RFC 8702.
615              
616             my $sig = $pk->sign_message($message, 'SHAKE128', 'pss', 32); # id-RSASSA-PSS-SHAKE128
617             my $sig = $pk->sign_message($message, 'SHAKE256', 'pss', 64); # id-RSASSA-PSS-SHAKE256
618              
619             =head2 verify_message
620              
621             Returns C<1> if the signature is valid, C<0> otherwise.
622              
623             my $pk = Crypt::PK::RSA->new($pub_key_filename);
624             my $valid = $pk->verify_message($signature, $message);
625             #or
626             my $valid = $pk->verify_message($signature, $message, $hash_name);
627             #or
628             my $valid = $pk->verify_message($signature, $message, $hash_name, $padding);
629             #or
630             my $valid = $pk->verify_message($signature, $message, $hash_name, 'pss', $saltlen);
631              
632             # $hash_name ............... [string] 'SHA1' (DEFAULT, INSECURE), 'SHA256' or any other hash supported by Crypt::Digest
633             # $padding ................. [string] 'pss' (DEFAULT) or 'v1.5' or 'none' (INSECURE)
634             # $saltlen (only for pss) .. [integer] DEFAULT is 12
635              
636             =head2 sign_hash
637              
638             Returns the signature as a binary string.
639              
640             my $pk = Crypt::PK::RSA->new($priv_key_filename);
641             my $signature = $pk->sign_hash($message_hash);
642             #or
643             my $signature = $pk->sign_hash($message_hash, $hash_name);
644             #or
645             my $signature = $pk->sign_hash($message_hash, $hash_name, $padding);
646             #or
647             my $signature = $pk->sign_hash($message_hash, $hash_name, 'pss', $saltlen);
648             #or
649             my $signature = $pk->sign_hash($message_hash, $hash_name, 'pss', $saltlen, $mgf_hash_name);
650              
651             # $hash_name ................. [string] 'SHA1' (DEFAULT, INSECURE), 'SHA256' or any other hash supported by Crypt::Digest
652             # $padding ................... [string] 'pss' (DEFAULT) or 'v1.5' or 'none' (INSECURE)
653             # $saltlen (pss only) ........ [integer] DEFAULT is 12
654             # $mgf_hash_name (pss only) .. [string] MGF hash function name (DEFAULT: the $hash_name value)
655              
656             =head2 verify_hash
657              
658             Returns C<1> if the signature is valid, C<0> otherwise.
659              
660             my $pk = Crypt::PK::RSA->new($pub_key_filename);
661             my $valid = $pk->verify_hash($signature, $message_hash);
662             #or
663             my $valid = $pk->verify_hash($signature, $message_hash, $hash_name);
664             #or
665             my $valid = $pk->verify_hash($signature, $message_hash, $hash_name, $padding);
666             #or
667             my $valid = $pk->verify_hash($signature, $message_hash, $hash_name, 'pss', $saltlen);
668             #or
669             my $valid = $pk->verify_hash($signature, $message_hash, $hash_name, 'pss', $saltlen, $mgf_hash_name);
670              
671             # $hash_name ................. [string] 'SHA1' (DEFAULT, INSECURE), 'SHA256' or any other hash supported by Crypt::Digest
672             # $padding ................... [string] 'pss' (DEFAULT) or 'v1.5' or 'none' (INSECURE)
673             # $saltlen (pss only) ........ [integer] DEFAULT is 12
674             # $mgf_hash_name (pss only) .. [string] MGF hash function name (DEFAULT: the $hash_name value)
675              
676             =head2 is_private
677              
678             my $rv = $pk->is_private;
679             # 1 .. private key loaded
680             # 0 .. public key loaded
681             # undef .. no key loaded
682              
683             =head2 size
684              
685             my $size = $pk->size;
686             # returns key size in bytes or undef if no key loaded
687              
688             =head2 key2hash
689              
690             Returns a hashref with the key components, or C if no key is loaded.
691              
692             my $hash = $pk->key2hash;
693              
694             # returns hash like this (or undef if no key loaded):
695             {
696             type => 1, # integer: 1 .. private, 0 .. public
697             size => 256, # integer: key size in bytes
698             # all the rest are hex strings
699             e => "10001", #public exponent
700             d => "9ED5C3D3F866E06957CA0E9478A273C39BBDA4EEAC5B...", #private exponent
701             N => "D0A5CCCAE03DF9C2F5C4C8C0CE840D62CDE279990DC6...", #modulus
702             p => "D3EF0028FFAB508E2773C659E428A80FB0E9211346B4...", #p factor of N
703             q => "FC07E46B163CAB6A83B8E467D169534B2077DCDEECAE...", #q factor of N
704             qP => "88C6D406F833DF73C8B734548E0385261AD51F4187CF...", #1/q mod p CRT param
705             dP => "486F142FEF0A1F53269AC43D2EE4D263E2841B60DA36...", #d mod (p - 1) CRT param
706             dQ => "4597284B2968B72C4212DB7E8F24360B987B80514DA9...", #d mod (q - 1) CRT param
707             }
708              
709             =head1 OpenSSL interoperability
710              
711             ### let's have:
712             # RSA private key in PEM format - rsakey.priv.pem
713             # RSA public key in PEM format - rsakey.pub.pem
714             # data file to be signed or encrypted - input.data
715              
716             =head2 Encrypt by OpenSSL, decrypt by Crypt::PK::RSA
717              
718             Create encrypted file (from commandline):
719              
720             openssl rsautl -encrypt -inkey rsakey.pub.pem -pubin -out input.encrypted.rsa -in input.data
721              
722             Decrypt file (Perl code):
723              
724             use Crypt::PK::RSA;
725             use Crypt::Misc 'read_rawfile';
726              
727             my $pkrsa = Crypt::PK::RSA->new("rsakey.priv.pem");
728             my $encfile = read_rawfile("input.encrypted.rsa");
729             my $plaintext = $pkrsa->decrypt($encfile, 'v1.5');
730             print $plaintext;
731              
732             =head2 Encrypt by Crypt::PK::RSA, decrypt by OpenSSL
733              
734             Create encrypted file (Perl code):
735              
736             use Crypt::PK::RSA;
737             use Crypt::Misc 'write_rawfile';
738              
739             my $plaintext = 'secret message';
740             my $pkrsa = Crypt::PK::RSA->new("rsakey.pub.pem");
741             my $encrypted = $pkrsa->encrypt($plaintext, 'v1.5');
742             write_rawfile("input.encrypted.rsa", $encrypted);
743              
744             Decrypt file (from commandline):
745              
746             openssl rsautl -decrypt -inkey rsakey.priv.pem -in input.encrypted.rsa
747              
748             =head2 Sign by OpenSSL, verify by Crypt::PK::RSA
749              
750             Create signature (from commandline):
751              
752             openssl dgst -sha256 -sign rsakey.priv.pem -out input.sha256-rsa.sig input.data
753              
754             Verify signature (Perl code):
755              
756             use Crypt::PK::RSA;
757             use Crypt::Digest 'digest_file';
758             use Crypt::Misc 'read_rawfile';
759              
760             my $pkrsa = Crypt::PK::RSA->new("rsakey.pub.pem");
761             my $signature = read_rawfile("input.sha256-rsa.sig");
762             my $valid = $pkrsa->verify_hash($signature, digest_file("SHA256", "input.data"), "SHA256", "v1.5");
763             print $valid ? "SUCCESS" : "FAILURE";
764              
765             =head2 Sign by Crypt::PK::RSA, verify by OpenSSL
766              
767             Create signature (Perl code):
768              
769             use Crypt::PK::RSA;
770             use Crypt::Digest 'digest_file';
771             use Crypt::Misc 'write_rawfile';
772              
773             my $pkrsa = Crypt::PK::RSA->new("rsakey.priv.pem");
774             my $signature = $pkrsa->sign_hash(digest_file("SHA256", "input.data"), "SHA256", "v1.5");
775             write_rawfile("input.sha256-rsa.sig", $signature);
776              
777             Verify signature (from commandline):
778              
779             openssl dgst -sha256 -verify rsakey.pub.pem -signature input.sha256-rsa.sig input.data
780              
781             =head2 Keys generated by Crypt::PK::RSA
782              
783             Generate keys (Perl code):
784              
785             use Crypt::PK::RSA;
786             use Crypt::Misc 'write_rawfile';
787              
788             my $pkrsa = Crypt::PK::RSA->new;
789             $pkrsa->generate_key(256, 65537);
790             write_rawfile("rsakey.pub.der", $pkrsa->export_key_der('public'));
791             write_rawfile("rsakey.priv.der", $pkrsa->export_key_der('private'));
792             write_rawfile("rsakey.pub.pem", $pkrsa->export_key_pem('public_x509'));
793             write_rawfile("rsakey.priv.pem", $pkrsa->export_key_pem('private'));
794             write_rawfile("rsakey-passwd.priv.pem", $pkrsa->export_key_pem('private', 'secret'));
795              
796             Use keys by OpenSSL:
797              
798             openssl rsa -in rsakey.priv.der -text -inform der
799             openssl rsa -in rsakey.priv.pem -text
800             openssl rsa -in rsakey-passwd.priv.pem -text -inform pem -passin pass:secret
801             openssl rsa -in rsakey.pub.der -pubin -text -inform der
802             openssl rsa -in rsakey.pub.pem -pubin -text
803              
804             =head2 Keys generated by OpenSSL
805              
806             Generate keys:
807              
808             openssl genrsa -out rsakey.priv.pem 1024
809             openssl rsa -in rsakey.priv.pem -out rsakey.priv.der -outform der
810             openssl rsa -in rsakey.priv.pem -out rsakey.pub.pem -pubout
811             openssl rsa -in rsakey.priv.pem -out rsakey.pub.der -outform der -pubout
812             openssl rsa -in rsakey.priv.pem -passout pass:secret -des3 -out rsakey-passwd.priv.pem
813              
814             Load keys (Perl code):
815              
816             use Crypt::PK::RSA;
817              
818             my $pkrsa = Crypt::PK::RSA->new;
819             $pkrsa->import_key("rsakey.pub.der");
820             $pkrsa->import_key("rsakey.priv.der");
821             $pkrsa->import_key("rsakey.pub.pem");
822             $pkrsa->import_key("rsakey.priv.pem");
823             $pkrsa->import_key("rsakey-passwd.priv.pem", "secret");
824              
825             =head1 SEE ALSO
826              
827             =over
828              
829             =item * L
830              
831             =back
832              
833             =cut