=head1 NAME Net::FCP::Key::CHK - manage CHK keys. =head1 SYNOPSIS use Net::FCP::Key::CHK; my $key = new Net::FCP::Key::CHK; my $key = new_from_uri Net::FCP::Key::CHK $uri; my $key = new_from_data Net::FCP::Key::CHK $data, $metadata; ... more to come =head1 DESCRIPTION =back =head2 THE Net::FCP::Key::CHK CLASS =over 4 =cut package Net::FCP::Key::CHK; use Carp; use Digest::SHA1; use Crypt::Twofish; use MIME::Base64; use Net::FCP::Util; no warnings; =item my $key = new Net::FCP::Key::CHK; Don't use :) =cut sub new { my $class = shift; bless { }, $class; } =item my $key = new_from_data Net::FCP::Key::CHK $metadata, $data; Generate a CHK from the given data and metadata strings. =cut sub new_from_data { my ($class, $metadata, $data) = @_; $class->new->set_data ($metadata, $data); } sub rolling_hashpad($$) { my $d = Digest::SHA1->new->add ($_[0]); my $pad = ""; while ($_[1] > length $_[0]) { my $dig = $d->digest_noreset; $d->add ($dig); $pad .= $dig; $_[0] .= $pad; } substr $_[0], $_[1], length $_[0], ""; } sub encode_number($) { my $num = pack "N", $_[0]; $num =~ s/^\x00+//; pack "n a*", length $num, $num; } sub set_data { my ($self, $metadata, $data) = @_; my $total_len = (length $metadata) + (length $data); my $padded_len = 1 << Net::FCP::Util::log2 $total_len, 10; my $plaintext = "$metadata$data"; my $d = new Digest::SHA1; $d->add ($plaintext); $d = $d->digest; # only works for 128 bit keys my $k = new Digest::SHA1; $k->add ("\x00" x 1); $k->add ($d); my $hash = substr +(my $x=$k->digest), 0, 16; # extract leading 128 bit my $buf = ""; $buf .= pack "n a20", 20, Digest::SHA1::sha1 $hash; $buf .= encode_number $total_len; $buf .= encode_number length $metadata; $buf .= "\x00\x00"; rolling_hashpad $buf, 1 << Net::FCP::Util::log2 length $buf; my $pcfbc = new Crypt::Twofish $hash; my $pcfbr = "\x00" x 16; my $pcfb = 16; my $pcfb_enc = sub { if ($pcfb == 16) { $pcfb = 0; $pcfbr = $pcfbc->encrypt ($pcfbr); } (substr $pcfbr, $pcfb++, 1) ^= $_[0]; }; $buf = join "", map $pcfb_enc->($_), split //, $buf; my $senc = unpack "H*", $buf; rolling_hashpad $plaintext, $padded_len; $plaintext = join "", map $pcfb_enc->($_), split //, $plaintext; my $partsize = $padded_len > 16384 ? $padded_len >> 7 : $padded_len; $partsize = 16384 if $partsize < 16384; # up to 4MB my $dig = ""; for (my $ofs = ($padded_len-1) - ($padded_len-1) % $partsize; $ofs >= 0; $ofs -= $partsize) { $dig = Digest::SHA1::sha1 substr ($plaintext, $ofs, $partsize) . $dig; } my $sini = unpack "H*", $dig; my $s = sprintf "Document-header\xfe%s\xff" . "Initial-digest\xfe%s\xff" . "Part-size\xfe%x\xff" . "Symmetric-cipher\xfe%s\xff", $senc, $sini, $partsize, "Twofish"; warn "$s\n"; $s = Net::FCP::Util::encode_base64 +(Digest::SHA1::sha1 $s) . (pack "C", Net::FCP::Util::log2 $padded_len) . "\x03\x02"; print "$s\n"; $self; } =item $size = $key->size Returns the size of the data (in bytes). =cut =item $digest = $key->digest Return the store digest/hash. =cut =item $keynum = $key->keynumber Returns the keynumber (version?) =cut =item $chk = $key->chk Return the full CHK. =cut =back =head1 SEE ALSO L. =head1 BUGS Not heavily tested. =head1 AUTHOR Marc Lehmann http://www.goof.com/pcg/marc/ =cut my $data = do { local $/; <> }; my $metadata = substr $data, 0, 0x3f, ""; #my $metadata = substr $data, 0, 0x28, ""; my $key = Net::FCP::Key::CHK->new_from_data ($metadata, $data); #use Net::FCP; my $fcp = new Net::FCP; warn $fcp->generate_chk ($metadata, $data); 1;