unless
(
grep
/^Net::SSH::Perl$/,
@DynaLoader::dl_modules
) {
XSLoader::load(
'Net::SSH::Perl'
);
}
sub
new {
my
$class
=
shift
;
my
$ciph
=
bless
{ },
$class
;
$ciph
->init(
@_
)
if
@_
;
$ciph
;
}
sub
keysize { 64 }
sub
blocksize { 8 }
sub
authlen { 16 }
sub
ivlen { 0 }
sub
init {
my
$ciph
=
shift
;
my
$key
=
shift
;
my
$size
=
$ciph
->keysize/2;
my
$mainkey
=
substr
(
$key
,0,
$size
);
my
$headerkey
=
substr
(
$key
,
$size
,
$size
);
$ciph
->{main} = Crypt::OpenSSH::ChachaPoly->new(
$mainkey
);
$ciph
->{header} = Crypt::OpenSSH::ChachaPoly->new(
$headerkey
);
}
sub
_seqnr_bytes {
my
(
$ciph
,
$seqnr
) =
@_
;
pack
(
'N'
,0) .
pack
(
'N'
,
$seqnr
);
}
sub
encrypt {
my
(
$ciph
,
$data
,
$seqnr
,
$aadlen
) =
@_
;
$aadlen
||= 0;
$seqnr
=
$ciph
->_seqnr_bytes(
$seqnr
);
$ciph
->{main}->ivsetup(
$seqnr
,
''
);
my
$poly_key
=
"\0"
x POLY1305_KEYLEN;
$poly_key
=
$ciph
->{main}->encrypt(
$poly_key
);
my
$aadenc
;
if
(
$aadlen
) {
$ciph
->{header}->ivsetup(
$seqnr
,
''
);
$aadenc
=
$ciph
->{header}->encrypt(
substr
(
$data
,0,
$aadlen
));
}
$ciph
->{main}->ivsetup(
$seqnr
,ONE);
my
$enc
=
$aadenc
.
$ciph
->{main}->encrypt(
substr
(
$data
,
$aadlen
));
$enc
.=
$ciph
->{main}->poly1305(
$enc
,
$poly_key
);
return
$enc
;
}
sub
decrypt {
my
(
$ciph
,
$data
,
$seqnr
,
$aadlen
) =
@_
;
$seqnr
=
$ciph
->_seqnr_bytes(
$seqnr
);
$ciph
->{main}->ivsetup(
$seqnr
,
''
);
my
$poly_key
=
"\0"
x POLY1305_KEYLEN;
$poly_key
=
$ciph
->{main}->encrypt(
$poly_key
);
my
$datalen
=
length
(
$data
) -
$ciph
->authlen;
if
(
$aadlen
) {
$datalen
=
unpack
(
'N'
,
$ciph
->{
length
} ||
eval
{
$ciph
->{header}->ivsetup(
$seqnr
,
''
);
$ciph
->{header}->decrypt(
substr
(
$data
,0,
$aadlen
))
});
}
delete
$ciph
->{
length
};
my
$expected_tag
=
$ciph
->{main}->poly1305(
substr
(
$data
,0,
$aadlen
+
$datalen
),
$poly_key
);
my
$tag
=
substr
(
$data
,
$aadlen
+
$datalen
,
$ciph
->authlen);
croak
"Invalid poly1305 tag"
if
$expected_tag
ne
$tag
;
$ciph
->{main}->ivsetup(
$seqnr
,ONE);
$ciph
->{main}->decrypt(
substr
(
$data
,
$aadlen
,
$datalen
));
}
sub
get_length {
my
(
$ciph
,
$data
,
$seqnr
) =
@_
;
return
if
length
(
$data
) < AADLEN;
$seqnr
=
$ciph
->_seqnr_bytes(
$seqnr
);
$ciph
->{header}->ivsetup(
$seqnr
,
''
);
$ciph
->{
length
} =
$ciph
->{header}->decrypt(
substr
(
$data
,0,AADLEN));
}
1;