#!/usr/bin/perl

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Module to simplify cross-language encryption/decryption w/ Blowfish.
# Copyright (C) 2006   Josh Kuo <josh.kuo@gmail.com>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301  USA
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

package Crypt;
use strict;
use warnings::register;

use Crypt::CBC;
use Digest::SHA qw(sha256_hex);
use MIME::Base64;

sub new {
    my $class = shift;
    my $self  = {
        CIPHER => undef,
    };
    bless $self, $class;
    $self->init(@_);
    return $self if $self;
}

sub decrypt {
    my $self = shift;
    my $encoded_encrypted = shift;
    my $encrypted = decode_base64($encoded_encrypted);
    my $decrypted = $self->{CIPHER}->decrypt($encrypted);
    chomp ($decrypted);
    return $decrypted;
}

sub encrypt {
    my $self = shift;
    my $plain_text = shift;
    my $encrypted = $self->{CIPHER}->encrypt($plain_text);
    my $encoded_encrypted = encode_base64($encrypted);
    chomp ($encoded_encrypted);
    return $encoded_encrypted;
}

sub init {
    my $self = shift;
    my $secret_phrase = shift;
    my $method = (caller(0))[3] . "()";
    my $digest = sha256_hex($secret_phrase);
    my $iv  = substr($digest, 0, 8);
    my $key = substr($digest, 8, 56);
    my $cipher = Crypt::CBC->new({
        'cipher'         => 'Blowfish',
        'iv'             => $iv,
        'key'            => $key,
        'padding'        => 'null',
        'prepend_iv'     => 0,
        'regenerate_key' => 0,
    });
    if (!$cipher && warnings::enabled()) {
        warnings::warn($self, "$method: could not create CBC cipher");
    }
    $self->{CIPHER} = $cipher;
}

1;

__END__

=head1 NAME

qrypto::Crypt - Module to simplify cross-language encryption/decryption w/ Blowfish.

=head1 SYNOPSIS

  use qrypto::Crypt;
  use qrypto::Crypt;
  use warnings 'qrypto::Crypt';
  use strict;
  my $qrypto = new Crypt('my ultra secret password');
  my $encrypted = $qrypto->encrypt('plain text goes here');
  my $decrypted = $qrypto->decrypt($encrypted);

=head1 METHODS

=head2 new

The constructor takes one argument, which is your secret password/passphrase.
Use it as follows:

  my $qryto = new Crypt('secret password');

=head2 decrypt

Reads the encrypted-encoded data, remove the Base64 encoding first, then
decrypt the binary data, and returns the plain text.

=head2 encrypt

Encrypts the input (plain text) into encrypted data encoded with Base64.

=head1 ALGORITHM

Blowfish requires two inputs, the IV (Initialization Vector) and the key.  The
key must be 56 in length.  The easiest and secure way to provide both inputs
is to take a single input (the pass-phrase) and run SHA256 on it to produce a
hex string that is 64 in length.  Use the first 8 for the IV, and the
remaining 56 for the key.  So it looks like this:

  $passphrase = 'this is a pass-phrase';
  $sha256 = '15343cba39004cd07d79ac972fa4bf50c7c504a8add5cb1c900e2263f82996f3';
      $iv = '15343cba';
      $key=         '39004cd07d79ac972fa4bf50c7c504a8add5cb1c900e2263f82996f3';

=head1 BASE64 ENCODING

Base64 Encoding:
================
The Blowfish encryption usually yields raw binary output, and when printed to
screen or stored to file, it looks like this:

    zФђпїЅпїЅпїЅE|sпїЅпїЅE^пїЅпїЅ

The biggest problem is when this is stored to a database table, and retrieving
it.  This not only messes up the terminal display, it sometimes cause some data
loss because some binary bits cannot be displayed or stored properly.  One of
the solutions is to change the database column type to BLOB instead of VARCHAR
(MySQL), but sometimes that may not be desirable.  To side step this problem,
encode the binary output with Base64, so the garabe you see above becomes the
following ASCII string:

    etSQutPwRXxzjclFXuC29g==

=head1 AUTHOR

Josh Kuo <josh.kuo@gmail.com> <josh.kuo@qbangsolutions.com>
http://www.qbangsolutions.com


=head1 COPYRIGHT AND LICENSE

Copyright 2007 by Josh Kuo

This library is free software; you can redistribute it and/or modify it under
the terms of LGPL.

=cut