NAME

PDF::Reuse::Scramble - Scramble data transfer between Perl - Acrobat JavaScript

SYNOPSIS

A little test where everything is done in Perl, 'test.pl'. In a real case, JavaScript would be involved.

  use PDF::Reuse::Scramble;
  use strict;

  #############################################################
  # As there are no userid or password as parameters to new(),
  # the system will ask for those two strings. (Don't use 
  # characters from an extended character set. Your operating
  # system and Acrobat/Reader might have different opinion
  # about character and numeric order)
  #############################################################

  my $s = PDF::Reuse::Scramble->new();

  my ($longKey, $shortKey, $transaction) = $s->getKeys();

  my $codedString = $s->encrypt('This text will be used');
  print "$codedString\n";

  ####################################################################
  # encrypt uses the short key by default, so that one has to be used
  # here for decryption. (If it had been encrypted by JavaScript, on
  # the other hand, the long key would have been used)
  ####################################################################
  
  my $d = PDF::Reuse::Scramble->decryptInit(key         => $shortKey,
                                            transaction => $transaction);
  my $text = $d->decrypt($codedString);
  print "$text\n";

DESCRIPTION

This module should be considered as experimental, because of the "feature" of Acrobat described further down under "Remarks about Acrobat and JavaScript".

A:

If your users have Acrobat Reader 4.0 and higher, you can use this module to verify who updated an interactive PDF-form. It creates an off-line login/authorization routine in JavaScript and it has encryption/decryption functions.

The user fills-in the form and just before he sends data to your server, he has to identify himself with a userid and password. If it seemed like he has identified himself correctly, his data is encrypted and sent. Your server needs a long key produced from his userid, password and a transaction code to be able read the messages correctly.

B:

If your users have Acrobat Reader 5.0.5 or higher it also makes it possible to use the Reader and Acrobat in a restricted way so you can decide who can read the data of the form. Also answers can be sent by mail.

(The Reader needs to have the option "Allow File Open Actions and Launching File Attachments" checked under "Preferences".)

The process then looks a little like this:

Data to be inserted in a PDF-document is encrypted with a short key and a transaction code.

When the user opens the document for the first time, he has to be authorized. He will be prompted for his userid and password, if this dialog hasn't been suppressed. Encryption keys and an internal match string are recreated. If the new and old match strings are equal, there is a good chance that the new keys also are correct, but there is no guarantee ! (The match strings are only present to help the user avoid small spelling mistakes. Big errors might not be detected.)

When data is sent back to the server, a long key together with a transaction code will be used both to encrypt and decrypt it.

Remarks about Acrobat and JavaScript

If your user has Acrobat Reader, he/she should be able to use the documents produced by this module. If he/she uses Acrobat there is a complication. Everything should work fine as long as new files are not read via the web, because Acrobat has a plug in, "webpdf.api", which converts documents, also PDF-documents !!!, when they are fetched. Most often JavaScripts are lost in this conversion. Documents sent by e.g. mail or ftp are not affected.

Encryption methods

new

 new( user        => $user,
      password    => $passWord,
      seed        => $seed,
      noUser      => $nouser,
      noPassword  => $nopassword,
      title       => $title,
      transaction => $trans,
      warning     => $message);

Creates a new instance of an encryption object.

All the parameters are optional. If 'noUser' is set to something, Acrobat/Reader will not prompt for an userid when the generated PDF-document is opened, and the system will generate an internal userid, if it has not been given as the 'user' parameter. The same goes for 'noPassword'. The user will not be prompted for it, and an internal one will be generated if it is not given by the 'password' parameter.

Both userid and password have to be at least 5 characters long.

Seed will be used for 'srand'. If it is not specified, 'time' will be used.

Title is the title of the password dialog in the PDF-document.

Transaction is a key component in the scrambling. By default it is random string, around 20 bytes long, more or less unique for each document. It can be any string, perhaps the same string for all users and every context, but then the encrypted data is not connected to a special document. It has to be at least 5 characters long.

Warning is the message you get if login/authorization has failed. If you don't define anything it will be: 'Authorization failed, encrypted data will not be shown'.

decrypt

$text = $s->decrypt($encodedString)

returns a decrypted string. It uses the long key to decrypt.

encrypt

$encoded = $s->encrypt($stringToEncrypt [,$key])

returns an hex-encoded string which has been encrypted/scrambled

$key is optional. Don't use this parameter when you send encrypted data to a PDF-document. The JavaScripts use only the short key to decrypt and the long key to encrypt.

exportJsCode

Saves the generated JavaScript functions 'authorize', 'encrypt' and 'decrypt' in the PDF-document you are creating. Nothing is initiated to run when the document is opened the first time.

fieldValue

$s->fieldValue($fieldName, $stringToEncrypt);

Encrypts a string. Makes the JavaScript function decrypt to be called when the PDF-document is opened, and makes the decrypted value to be assigned to the field with the name $fieldName.

N.B. Your user needs Acrobat/Reader 5.0.5 or higher.

getKeys

($fullKey, $halfKey, $transaction) = $s->getKeys();

Returns the keys used in the scrambling

initJsCode

$s->initJsCode();

Inserts a string with JavaScript code in the PDF-document you are creating. It will be the JavaScript functions 'authorize', 'encrypt' and 'decrypt'. 'authorize' will be initiated to run when the document is opened the first time.

N.B. Your user needs Acrobat/Reader 5.0.5 or higher.

Decryption methods

initDecrypt

$d->initDecrypt(key         => $key,
                transaction => $transactionCode);

Creates a new decryption object.

decrypt

$text = $d->decrypt($encodedString)

returns a decrypted string. Uses what has been defined as 'key' to decrypt.

Example

This should work for Acrobat/Reader 4.0 and higher. (So it should probably work for most users.)

You have a fill-in form and now you are going to send it to a customer by mail. You want to be sure that the one, who sends the answer back to you, really is your customer and nobody else.

The name of the fill-in form is 'old.pdf'. It has a number of interactive fields and a button which calls sendUpdates(); when you click on it.

This JavaScript is defined at document level:

function sendUpdates()
  {  //
     // To be sure that the keys are defined
     //

     authorize();

     //
     // If the authorizing failed, nothing will be sent
     //

     if (! fullKey)
        return;
     else
     {  var str = "Will try to send data." + "\r You will get a response. If not\r"
                + "- you have to activate your internet connection and retry !";
        app.alert(str);
     }
     var str = 'r=' + scramble + '&' + 're=' + encrypt(scramble) + '&';
     for (var i = 0; i < this.numFields; i++)
     {   var theName = this.getNthFieldName(i);
         var f = this.getField(theName);
         if ((f.type == 'text') && (f.defaultValue != f.value))

         // 
         // Field values are encrypted
         //

         {   str = str + theName + '=' + encrypt(f.value) + '&';}
     }
     var dest = 'http://127.0.0.1:80/cgi-bin/update.pl?cust=' 
                                 + getCust() + '&' + str; 
     this.getURL(dest, false);
  }

Here is the program, 'verified.pl' that generates the PDF-document

  use PDF::Reuse;
  use PDF::Reuse::Scramble;
  use strict;

  my $transaction = 'Registration';              # Case sensitive !

  ##########################################
  # Data about the customer from a database
  ##########################################

  my $userId   = 'Adam Andersson';               # Case sensitive !
  my $password = 'The 4th of July 1996';         # Case sensitive !
  my $custNo   = 20456; 

  prFile('verify.pdf');
  prCompress(1);
  my $s = PDF::Reuse::Scramble->new( user        => $userId,
                                     password    => $password,
                                     transaction => $transaction);

  #######################################################
  # The adjusted JavaScript functions authorize, encrypt
  # and decrypt are saved in the generated PDF-document
  #######################################################
  
  $s->exportJsCode();

  ########################################################
  # A little JavaScript for customer number is also saved
  ########################################################

  prJs("function getCust() { return '$custNo'; }");

  prDoc('old.pdf');
  prEnd();

When the user presses the button, he has to identify himself with a userid and password, and the data, partly encrypted and hex encoded is sent.

At the server side some data is needed to decrypt the answer, among other things:

  # ...

  ############################################################
  # Customer number and transaction from the received message
  ############################################################

  my $custNo   = 20456;
  my $transaction = 'Registration';

  ##########################################
  # Data about the customer from a database
  ##########################################

  my $userId   = 'Adam Andersson';
  my $password = 'The 4th of July 1996'; 

  ################################################################ 
  # a 'Scramble' object is created. In it, the keys are recreated
  ################################################################

  my $s = PDF::Reuse::Scramble->new(user        => $userId,
                                    password    => $password,
                                    transaction => $transaction);

  # ...

  #######################################################################
  # Each encrypted and hex encoded string can now be decrypted like this 
  #######################################################################

  my $text = $s->decrypt($encodedString);

  ##########################################################################
  # if the value of 'valueIf' = 'It is valid', the right customer answered,
  # and the values of the other fields should also be reliable
  ##########################################################################

Examples for Acrobat 5.0/Reader 5.0.5 or higher

Sooner or later most users have upgraded to at least Acrobat/Reader 5.0.5 and then these examples can be used.

A short example

You have a PDF-document with the interactive fields: field_1, field_2 and field_3 (spelled exactly like that), which you want to fill with encrypted text. If your user writes the control code "AX225", he/she will be able to read the encrypted texts.

use PDF::Reuse;
use PDF::Reuse::Scramble;
use strict;

prFile('hidden1.pdf');
prCompress(1);
my $s = PDF::Reuse::Scramble->new( nouser => 1,
                                   password => 'AX225',
                                   title    => 'Control Code');
$s->initJsCode();
$s->fieldValue('field_1', 'This is the first secret');
$s->fieldValue('field_2', 'This is the second secret');
$s->fieldValue('field_3', 'This is the third secret');
prDoc('old.pdf');
prEnd();

A long example (more or less complete)

This example looks a little bit big, because it shows how different programs interact. It is two Perl programs and also JavaScript embedded in a generated PDF-document. And I have also tried to make it a 'complete example'. Cut and paste, save it as files and try it. If you have a local web server, you could run the programs at the directory where your server looks for CGI-programs.

First we need a JavaScript file which defines interactive fields and buttons and assigns values to them ('fab.js'):

 function fab()   
  {  var param = fab.arguments;       // Here are the parameters
     var page  = param[0];            // The first 3 parameters 
     var x     = param[1];            // are not encrypted
     var y     = param[2];
     var l;     
     var d;
     var labelText = [ "Mr_Ms", "First_Name", "Surname",
                       "Address", "City", "Zip_Code", "Country",
                       "Phone", "Mobile_Phone", "E-mail",
                       "Company", "Order_1", "Order_2",
                       "Order_3" ];   
     var k = 3;
     for ( var i = 0; i < labelText.length; i++)
     {   l = x + 80;               // length of the label
         d = y - 15;               // depth / hight

         //
         // a label field is created
         //

         var fieldName = labelText[i] + "Label";
         var lf1       = this.addField(fieldName, "text", page, [x,y,l,d]);
         lf1.fillColor = color.white;
         lf1.textColor = color.black;
         lf1.readonly  = true;
         lf1.textSize  = 12;
         lf1.defaultValue = labelText[i];
         lf1.value     = labelText[i];
         lf1.display   = display.visible;

         //
         // a text field for the customer to fill-in his/her data is created 
         //
 
         x = l + 2;
         l = x + 200;
         var tf1         = this.addField(labelText[i], "text", page, [x,y,l,d]);
         tf1.fillColor   = ["RGB", 1, 1, 0.94];
         tf1.strokeColor = ["RGB", 0.7, 0.7, 0.6];
         tf1.textColor   = color.black;
         tf1.borderStyle = border.s;
         tf1.textSize    = 12;
         tf1.display     = display.visible;

         //
         // Here below encrypted parameters are handled
         // 

         if (param[k])
         {   tf1.value        = decrypt(param[k]);
             tf1.defaultValue = tf1.value;
         }
         x = x - 82    // move 82 pixels to the left
         if (i == 3)
         {  y = y - 90;}
         else
         {  y = y - 17;   // move 17 pixels down
         }
         k++;
     }

     //
     // The update button is created
     //

     y = y + 34;
     x = x + 310;
     l = x + 75;
     d = y - 30;
     var f = this.addField("ButUpdate","button", page , [x,y,l,d]);
     f.setAction("MouseUp", "sendUpdates()");
     f.userName = "To send updated data (and get a response)";
     f.buttonSetCaption("Update");
     f.borderStyle = border.b;
     f.fillColor   = ["RGB", 0.3, 0.7, 0.3];
     
     //
     // The mail button
     //

     x = x + 100;
     l = x + 75;
     var m = this.addField("ButMail","button", page , [x,y,l,d]);
     m.setAction("MouseUp", "mailUpdates()");
     m.userName = "Send data from this form by e-mail";
     m.buttonSetCaption("Mail");
     m.borderStyle = border.b;
     m.fillColor   = ["RGB", 1, 0.8, 0.4];          
  }

  function sendUpdates()
  {  //
     // To be sure that the keys are defined
     //

     authorize();

     //
     // If the authorizing failed, nothing will be sent
     //

     if (! fullKey)
        return;
     else
     {  var str = "Will try to send data." + "\r You will get a response. If not\r"
                + "- you have to activate your internet connection and retry !";
        app.alert(str);
     }
     var str = 'r=' + scramble + '&' + 're=' + encrypt(scramble) + '&';
     for (var i = 0; i < this.numFields; i++)
     {   var theName = this.getNthFieldName(i);
         var f = this.getField(theName);
         if ((f.type == 'text') && (f.defaultValue != f.value))

         // 
         // Field values are encrypted
         //

         {   str = str + theName + '=' + encrypt(f.value) + '&';}
     }
     var dest = 'http://127.0.0.1:80/cgi-bin/update.pl?cust=' 
                                 + getCust() + '&' + str; 
     this.getURL(dest, false);
  }
  function mailUpdates()
  {  authorize();
     if (! fullKey)
        return;
     var str = 'cust=' + getCust() + '&';
     for (var i = 0; i < this.numFields; i++)
     {   var theName = this.getNthFieldName(i);
         var f = this.getField(theName);
         if ((f.type == 'text') && (f.defaultValue != f.value)) 
         {   str = str + theName + '=' + encrypt(f.value) + '&';}
     }
     app.mailMsg( {bUI: true, cTo: "com@company.com", 
                   cSubject: "This is the subject", cMsg: str} );
  }

Here is a Perl program which creates a PDF-document and uses 'fab.js'

use PDF::Reuse;
use PDF::Reuse::Scramble;
use strict;

#####################################################
# Data about the customer, should be from a database
#####################################################

my $customerNo   = 5;  
my @customerData = ('Mr', 'Peter', 'Johansson', 'Kungsgatan 9', 'Olovstad',
                    'SE-10010', 'Sweden', '+46119-23456', '+4670-777777',
                    'pj@com', 'Tot. Invented Inc.');

my $userId       = '12Peter';                 # Case sensitive !
my $password     = 'Crazy Horse';             # Case sensitive !

#############################################################
# The document should be compressed so the keys and para- 
# meters are not directly visible (but it is not a big deal  
# if they are seen), and it will have a more acceptable size
#############################################################

prFile('hidden.pdf');
prCompress(1);

#########################################################
# Keys are calculated and JavaScript code is "generated"
#########################################################

my $s = PDF::Reuse::Scramble->new(user     => $userId,
                                  password => $password);

######################################################################
# The JavaScript functions "authorize", "crypt" and "decrypt" will be 
# inserted. "authorize" will run when the document is opened
######################################################################

$s->initJsCode();

###############################################################
# Here the full decryption key and transaction code are saved 
# in a file instead of a database, just for this demonstration
###############################################################

my $secrets = 'run.txt';
my ($fullKey, $halfKey, $transaction) = $s->getKeys();
open (OUTFILE, ">$secrets") || die $!;
print OUTFILE "$fullKey\n";
print OUTFILE "$transaction";
close OUTFILE;

############################################
# A function which always will give a fixed
# value for this PDF-document
############################################

prJs("function getCust() { return '$customerNo'; }");

########################################################
# Data for the fill-in form is assigned and "encrypted"
########################################################

my $parameters = "0, 100, 800";
for (@customerData)
{  $parameters .= ",'" . $s->encrypt($_) . "'";
}
##########################################################
# The JavaScript functions for the fill-in form is added 
# and initiated
##########################################################

prJs('fab.js');
my $jsCode = "fab($parameters)";
prInit($jsCode);

###########################################
# An interactive field in the PDF-document  
# will get an encrypted value 
###########################################

$s->fieldValue('Order_2','Something really secret');

prEnd();

And here at last is a little cgi-program 'update.pl' which receives encrypted data from the PDF-document, decrypts it, and sends the result back.

use PDF::Reuse;
use PDF::Reuse::Scramble;
use strict;

my $x = 25;
my $y = 790;
my $step = 18;
my ($string, %data, $value, $key);

###############################
# First get data to work with 
###############################

if ( $ENV{'REQUEST_METHOD'} eq "GET" 
&&   $ENV{'QUERY_STRING'}   ne '') 
{  $string = $ENV{'QUERY_STRING'};
}

###########################################
# Split and decode the hex-encoded strings
# Create a hash with user data
###########################################
for my $pair (split('&', $string)) 
{  if ($pair =~ /(.*)=(.*)/)                     # found key=value;
   {   ($key,$value) = ($1,$2);                  # get key, value.
        $value =~ s/\+/ /g;
        $value =~ s/%(..)/pack('C',hex($1))/eg;  # Not really necessary here
        $data{$key} = $value;                    # Create the hash.
   }
}

#######################
# Get the secret data
#######################

my $infile = 'run.txt';
open (INFILE, "$infile");
my $fullKey = <INFILE>;
my $transaction  = <INFILE>;
close INFILE;
chomp($fullKey);

#############################
# Create a decryption object
#############################

my $d = PDF::Reuse::Scramble->decryptInit(key         => $fullKey,
                                          transaction => $transaction);

####################
# Create new output
####################

$| = 1;
print STDOUT "Content-Type: application/pdf \n\n";

prFile();
prCompress(1);
prTouchUp(0);
prFontSize(16);

###################################################################
# First a little check that the unencrypted transaction code, and 
# the decrypted one are equal. In a real situation, it is probably  
# not a good idea to put two such values close to each other 
###################################################################

if ((exists $data{'r'}) && (exists $data{'re'}))
{  if ( $data{'r'} eq $d->decrypt($data{'re'}))
   {  prText($x, $y, "The messages are valid");
   }
   else
   {  prText($x, $y, "The messages are invalid");
   }
   $y -= $step * 3;
}

##############################################
# The transferred data is decrypted and shown
##############################################

for $key (keys %data)
{  if (($key eq 'cust') || ($key eq 'r'))
   {  prText($x, $y, "$key : $data{$key}");
   }
   else
   {  my $str = $d->decrypt($data{$key});
      prText($x, $y, "$key : $str");
   }
   $y -= $step;
   if ($y < 40)
   {  prPage();
      $y = 790;
   }
}
prEnd();

SEE ALSO

PDF::Reuse

PDF::Reuse::Tutorial

Extracting the programs of this document

At the beginning of the POD of PDF::Reuse::Tutorial, from version 0.09, there is a snippet of code, which can be used to extract the programs from this POD.

AUTHOR

Lars Lundberg, Solidez HB, elkelund@worldonline.se

COPYRIGHT AND LICENSE

Copyright 2003 - 2004 by Lars Lundberg

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

DISCLAIMER

I haven't worked very much with cryptography, so I am grateful for all suggestions regarding this module.

You get this module free as it is, but nothing is guaranteed to work, whatever implicitly or explicitly stated in this document, and everything you do, you do at your own risk - I will not take responsibility for any damage, loss of money and/or health that may arise from the use of this document!