NAME
PDF::Reuse::Scramble - Scramble data transfer between Perl - Acrobat JavaScript
SYNOPSIS
A little test where everything is done in Perl. In a real case JavaScript should 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";
ABSTRACT
This module has subroutines in Perl and functions in Acrobat JavaScript to encrypt and decrypt data transferred between Perl and a PDF-document and back to Perl. There is also a subroutine/function in both languages to create the keys used for the scrambling.
DESCRIPTION
This is an experimental module. It should work for Acrobat Reader 5.0.5 or higher. It makes it possible to use the Reader and also Acrobat in a restricted way, so you can decide who can read the data you send out, and when you get a response, you can know that the right person answered.
When you use this module to encrypt data for an interactive PDF-document, the process 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.
Methods
new
new( user => $user,
password => $passWord,
seed => $seed,
noUser => $nouser,
noPassword => $nopassword,
title => $title,
transaction => $trans);
Creates a new instance of an encryption object.
All the parameters are optional. If 'noUser' is set to something, Acrobat/Reader will NOT prompted 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.
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.
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.
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.
initDecrypt
$d->initDecrypt(key => $key,
transaction => $transactionCode);
Creates a new decryption object.
decrypt
$text = $d->decrypt($encodedString)
returns a decrypted string
EXAMPLES
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 you write the control code "AX225", you 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 = "Press here to send updated data from this form";
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 = "Press here to 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;
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 parameters are not directly visible (but
# it is not a big deal if they are seen)
#################################################
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 random string and the encrypted one
# are equal. In a real situation, it is probably NOT a good idea
# to put an encrypted and unencrypted value 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
AUTHOR
Lars Lundberg, Solidez HB, elkelund@worldonline.se
COPYRIGHT AND LICENSE
Copyright 2003 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!