The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
#Copyright barry king <barry@wyrdwright.com> and released under the GPL.
#See http://www.gnu.org/licenses/gpl.html#TOC1 for details
use 5.006;
use strict;
use warnings;
no warnings qw(uninitialized);

package Apache::Wyrd::Services::CodeRing;
our $VERSION = '0.98';
use Apache::Wyrd::Services::SAK qw(lc_hash);
use Apache::Wyrd::Services::Key;
use Digest::SHA qw(sha1_hex);

my $pure_perl = 0;
eval ('use Crypt::Blowfish');
if ($@) {
	eval ('use Crypt::Blowfish_PP');
	die "$@" if ($@);
	$pure_perl = 1;
}

#Initialize Key.  Assumes startup with Apache, will stay resident as a class constant
my $key = Apache::Wyrd::Services::Key->instance();

=pod

=head1 NAME

Apache::Wyrd::Services::CodeRing - Apache-resident crypto tool (Blowfish)

=head1 SYNOPSIS

    my $cr1 = Apache::Wyrd::Services::CodeRing->new;
    my $key = $cr1->key;
    my $secret = "The turtle moves!"
    my $cytext = $cr1->encrypt($secret);

    my $cr2 = Apache::Wyrd::Services::CodeRing->new({key => $key});
    my $plaintext = ($cr2->decrypt($crptext)
      || die "Key or cypher text was corrupt");

=head1 DESCRIPTION

The CodeRing is an encryption/decryption object for use primarily for
encrypting state information into cookies or hidden variables without
exposing the data to deconstruction or corruption in transference.

It uses the blowfish algorithm via either a Crypt::Blowfish or
Crypt::Blowfish_PP module, depending on which one compiles on this
system, preferring the C-based one.

The CodeRing uses an internal hashing algorithm (SHA) to check the
validity of the decrypt.  If the decrypt shows alteration, it returns an
empty string.

Unless the CodeRing is given a key on initialization, it uses an
instance of the C<Apache::Wyrd::Services::Key> class, which is designed to
be a constant in primary server memory space.  The Key, in this case, is
"known" only to the Apache process, and is regenerated on each restart.

=head2 HTML ATTRIBUTES

=over

=item attribute

attribute description

=back

=head2 PERL METHODS

I<(format: (returns) name (arguments after self))>

=over

=item (Apache::Wyrd::Services::CodeRing) C<new> ([hashref])

Create a new CodeRing.  Accepts an optional hashref initialization.  The
hashref can have a key, B<key>, the value of which will be the key used
for encryption and decryption.

=cut

sub new {
	my ($class, $init) = @_;
	$init = lc_hash($init);
	my $instance_cypher = $key->cypher;
	if ($pure_perl) {
		$instance_cypher = Crypt::Blowfish_PP->new($$init{'key'}) if ($$init{'key'});
	} else {
		$instance_cypher = Crypt::Blowfish->new($$init{'key'}) if ($$init{'key'});
	}
	my $data = {
		cypher => $instance_cypher,
		key => ($$init{'key'} || $key->key)
	};
	bless $data, $class;
	return $data;
}

=item (scalar) C<key> (void)

Return the value of the current key.

=cut

sub key {
	my $self = shift;
	return $self->{'key'};
}

=pod

=item (scalarref) C<encrypt> (scalarref)

Encrypt the text referred to by the argument.  Returns a scalarref.

=cut

sub encrypt {
	my ($self, $textref) = @_;
	die ("you must use a scalar ref in encrypt at " . join(':', caller)) unless (ref($textref) eq 'SCALAR');
	my ($i, @out, $block, $cyphertext) = ();
	#Note: 7 nulls are added to ensure a full final octet.
	my $crc = sha1_hex($$textref);
	my @in = split ('', $$textref . "\0". $crc . "\0\0\0\0\0\0\0\0");
	while ($#in > 0) {
		$block = $self->{'cypher'}->encrypt(pack('a8', join('', splice (@in, 0, 8))));
		push (@out, unpack('H*', $block));
	}
	$cyphertext = join ('', @out);
	return \$cyphertext;
}

=pod

=item (scalarref) C<decrypt> (scalarref)

Decrypt the text referred to by the argument.  Returns a scalarref.  The
scalarref is zero-length on a failed decrypt.

=cut

sub decrypt {
	my ($self, $textref) = @_;
	die ("you must use a scalar ref in decrypt at " . join(':', caller)) unless (ref($textref) eq 'SCALAR');
	my ($d, $block, $plaintext) = ();
	my @in = split('', $$textref);
	while ($#in > 0) {
		$block = '';
		while (length($block) < 8){
			$d = chr(hex(join('', (splice(@in, 0, 2)))));
			#last unless ($d);
			$block .= $d;
		}
		$plaintext .= $self->{'cypher'}->decrypt($block);
	}
	#remove tail nulls and all trailing garbage
	$plaintext =~ s/\0([A-Fa-f0-9]{40})\0*.*$//s;
	my $crc = sha1_hex($plaintext);
	#If the CRC check fails, assume the key is bad and return null;
	$plaintext = '' if ($crc ne $1);
	return \$plaintext
}

=pod

=back

=head1 BUGS/CAVEATS/RESERVED METHODS

Rather than returning an error, the C<decrypt> method silently returns a
ref to an empty string on an unsuccessful decrypt.  The null byte ("\0")
is used internally as a string terminator.  Any item encrypted
containing null bytes will not successfully decrypt.

=head1 AUTHOR

Barry King E<lt>wyrd@nospam.wyrdwright.comE<gt>

=head1 SEE ALSO

=over

=item Apache::Wyrd::Services::Key

Shared-memory encryption key and cypher.

=back

=head1 LICENSE

Copyright 2002-2007 Wyrdwright, Inc. and licensed under the GNU GPL.

See LICENSE under the documentation for C<Apache::Wyrd>.

=cut

1;