Crypt::EAX - Encrypt and authenticate data in EAX mode


Crypt-EAX documentation Contained in the Crypt-EAX distribution.

Index


Code Index:

NAME

Top

Crypt::EAX - Encrypt and authenticate data in EAX mode

SYNOPSIS

Top

	use Crypt::EAX;

	my $c = Crypt::EAX->new(
		key => $key,
		cipher => "Crypt::Rijndael",
		header => $header, # optional
		nonce => $nonce, # optional but reccomended
		fatal => 1,
	);

	my $ciphertext = $c->encrypt( $message );

	$ciphertext ^= "moose"; # corrupt it

	$c->decrypt( $ciphertext ); # dies

	$ciphertext ^= "moose"; # xor is reversible

	is( $c->decrypt( $ciphertext ), $msg );

DESCRIPTION

Top

EAX is a cipher chaining mode with integrated message authentication. This type of encryption mode is called AEAD, or Authenticated Encryption with Associated Data.

The purpuse of AEAD modes is that you can safely encrypt and sign a value with a shared key. The message will not decrypt if it has been tampered with.

There are various reasons why just encrypt(mac($message)) is not safe, but I don't exactly know them since I'm not a crptographer. For more info use The Oracle Google.

Read more about EAX AEAD here:

http://en.wikipedia.org/wiki/EAX_mode
http://en.wikipedia.org/wiki/AEAD_block_cipher_modes_of_operation

CONFIGURATION

Top

key

The key used to encrypt/decrypt and authenticate. Passed verbatim to Crypt::Ctr::FullWidth and Digest::CMAC.

cipher

Defaults to Crypt::Rijndael. Likewise passed verbatim.

fatal

Whether or not failed verification dies or returns a false value.

Additional data to be authenticated but not encrypted.

Note that it's also possible to incrementally add the header using add_header.

If the header option is passed instead then add_header will be called with it as an argument every time reset is called.

This will not be included in the resulting ciphertext, but the ciphertext must be authenticated against it.

Presumably you are supposed to encode the ciphertext and header together in your message.

This is the Associated Data part of AEAD.

Be careful if you deconstruct the message naively, like this:

	my ( $header, $ciphertext ) = unpack("N/a a*", $message);

since you are inherently trusting the input data already, before it's been verified (the N/ part can be altered, and though knowing Perl this is probably safe, I wouldn't count on it). The specific attack in this case is if a large number is encoded by the attacker in the N field then it could trick your program into trying allocate 4GB of memory in this particular example.

At any rate do not trust the header till the ciphertext has been successfully decrypted.

nonce

The nonce to use for authentication. Should be unique. See http://en.wikipedia.org/wiki/Cryptographic_nonce.

It is OK to pass this along with the ciphertext, much like the salt bit in crypt.

An empty value is allowed and is in fact the default, but this is not safe against replay attacks.

METHODS

Top

new %args

Instantiate a new Crypt::EAX object.

See CONFIGURATION.

encrypt $plaintext
decrypt $ciphertext

Single step encryption/decryption.

The tag is appended to the ciphertext.

encrypt_parts $plaintext

Returns the ciphertext and tag as separate tags.

decrypt_parts $ciphertext, $tag

Decrypts and verifies the message.

start $mode

Takes either encrypting or decrypting.

finish ?$tag

If encrypting, returns the tag.

If decrypting, checks that $tag is equal to the calculated tag.

Used by encrypt_parts and decrypt_parts.

add_encrypt $text
add_decrypt $ciphertext

Streaming mode of operation. Requires a call to start before and finish after. Used by decrypt_parts and encrypt_parts.

add_header $header

Add header data that will be authenticated as well. See header for more details.

verification_failed

Called when verification fails. Dies when fatal is set, returns a false value otherwise.

TODO

Top

SEE ALSO

Top

Digest::CMAC, Crypt::Ctr, Crypt::Ctr::FullWidth, Crypt::Util

VERSION CONTROL

Top

This module is maintained using Darcs. You can get the latest version from http://nothingmuch.woobling.org/code, and use darcs send to commit changes.

AUTHOR

Top

Yuval Kogman <nothingmuch@woobling.org>

COPYRIGHT & LICENSE

Top


Crypt-EAX documentation Contained in the Crypt-EAX distribution.

#!/usr/bin/perl

package Crypt::EAX;
use Moose;

our $VERSION = "0.04";

use Carp qw(croak);

use Digest::CMAC;
use Crypt::Ctr::FullWidth;

use namespace::clean -except => [qw(meta)];

has key => (
	isa => "Str",
	is  => "ro",
	required => 1,
);

has [qw(header nonce)] => (
	isa => "Str",
	is  => "ro",
	default => '',
);

has mode => (
	isa => "Str",
	is  => "rw",
);

has N => (
	isa => "Str",
	is  => "rw",
	lazy => 1,
	default => sub {
		my $self = shift;
		$self->omac_t( $self->n_omac, 0, $self->nonce );
	},
);

has cipher => (
	#isa => "ClassName|Object",
	is  => "rw",
	default => "Crypt::Rijndael",
);

has fatal => (
	isa => "Bool",
	is  => "rw",
	default => 1,
);

has ctr => (
	isa => "Crypt::Ctr::FullWidth",
	is  => "ro",
	lazy => 1,
	default => sub {
		my $self = shift;
		Crypt::Ctr::FullWidth->new( $self->key, $self->cipher );
	},
);

has [qw(c_omac n_omac h_omac)] => (
	isa => "Digest::CMAC",
	is  => "ro",
	lazy => 1,
	default => sub {
		my $self = shift;
		Digest::CMAC->new( $self->key, $self->cipher );
	},
);

sub BUILDARGS {
	my ( $class, @args ) = @_;

	if ( @args == 1 ) {
		return { key => $args[0] }
	} elsif ( @args == 2 and $args[0] ne 'key' ) {
		return { key => $args[0], cipher => $args[1] }
	} else {
		return $class->SUPER::BUILDARGS(@args);
	}
};

sub _cbc_k {
	my ( $self, $m ) = @_;
	return;
}

sub reset {
	my $self = shift;

	$self->ctr->reset;
	$self->c_omac->reset;
	$self->h_omac->reset;

	$self->ctr->set_nonce($self->N);

	$self->omac_t( $self->c_omac, 2 );
	if ( length ( my $header = $self->header ) ) {
		$self->omac_t( $self->h_omac, 1, $header );
	}
}

sub BUILD {
	my ( $self, $args ) = @_;
	$self->omac_t( $self->h_omac, 1 );
	$self->reset;
}

sub start {
	my ( $self, $mode ) = @_;
	$self->mode($mode);
}

sub encrypt_parts {
	my ( $self, $plain ) = @_;

	$self->start('encrypting');

   	return ( $self->add_encrypt($plain), $self->finish );
}

sub encrypt {
	my ( $self, $plain ) = @_;
	return join('', $self->encrypt_parts($plain) );
}

sub decrypt_parts {
	my ( $self, $ciphertext, $tag ) = @_;

	$self->start('decrypting');

	my $plain = $self->add_decrypt( $ciphertext );

	if ( $self->finish($tag) ) {
		return $plain;
	} else {
		$self->verification_failed($ciphertext, $plain, $tag);
	}
}

sub decrypt {
	my ( $self, $ciphertext ) = @_;

	my $blocksize = $self->blocksize;

	$ciphertext =~ s/(.{$blocksize})$//s;
	my $tag = $1;

	$self->decrypt_parts( $ciphertext, $tag );
}

sub verification_failed {
	my $self = shift;

	if ( $self->fatal ) {
		croak "Verification of ciphertext failed";
	} else {
		return;
	}
}

sub add_header {
	my ( $self, $plain ) = @_;

	$self->h_omac->add( $plain );

	return;
}

sub add_encrypt {
	my ( $self, $plain ) = @_;

	my $ciphertext = $self->ctr->encrypt($plain) || '';

	$self->c_omac->add( $ciphertext );

	return $ciphertext;
}

sub add_decrypt {
	my ( $self, $ciphertext ) = @_;

	$self->c_omac->add( $ciphertext );

	my $plain = $self->ctr->decrypt($ciphertext);

	return $plain;
}

sub finish {
	my ( $self, @args ) = @_;

	die "No current mode. Did you forget to call start()?" unless $self->mode;

	my $tag = $self->tag;

	if ( $self->mode eq 'encrypting' ) {
		return $tag;
	} elsif ( $self->mode eq 'decrypting' ) {
		return 1 if $tag eq $args[0];
		return;
	} else {
		croak "Unknown mode: " . $self->mode;
	}
}

sub tag {
	my $self = shift;

	my $N = $self->N;
	my $H = $self->h_omac->digest;
	my $C = $self->c_omac->digest;

	$self->reset;

	return $N ^ $H ^ $C;
}

sub omac_t {
	my ( $self, $omac, $t, @msg ) = @_;

	my $blocksize = $self->blocksize;
	my $padsize = $blocksize -1;

	my $num = pack("x$padsize C", $t);

	$omac->add( $num );

	$omac->add( $_ ) for @msg;

	return $omac->digest if defined wantarray;
}

sub blocksize {
	my $self = shift;

	$self->c_omac->{cipher}->blocksize;
}

__PACKAGE__->meta->make_immutable if __PACKAGE__->meta->can("make_immutable");

__PACKAGE__;

__END__