1995-02-02 - Adding padding to PGP files

Header Data

From: Hal <hfinney@shell.portal.com>
To: cypherpunks@toad.com
Message Hash: 4dad69c196315f25f5fc09ae306634feda3e1dd567b5c89b5278c779e0ee62c9
Message ID: <199502021648.IAA05417@jobe.shell.portal.com>
Reply To: N/A
UTC Datetime: 1995-02-02 16:49:39 UTC
Raw Date: Thu, 2 Feb 95 08:49:39 PST

Raw message

From: Hal <hfinney@shell.portal.com>
Date: Thu, 2 Feb 95 08:49:39 PST
To: cypherpunks@toad.com
Subject: Adding padding to PGP files
Message-ID: <199502021648.IAA05417@jobe.shell.portal.com>
MIME-Version: 1.0
Content-Type: text/plain


Here are a couple of perl scripts I wrote last year to add padding to PGP
encrypted files.  The usage would be:

perl pgppadt.pl filename bytestoadd

The output file is filename.pad.

It only works on binary ".pgp" public-key encrypted files (not ascii armored
files).  So there would be some work needed to make it a really useful tool.
It would also be better to use a strong source of random numbers.  I
think Carl Ellison recently posted some tools that could help with this.
The two files are pgppad.pl, which does the work, and pgppadt.pl, a very
simple test driver to show how to use it.  They are in a shar archive.

Hal

---------------cut here----------------
#!/bin/sh
# to extract, remove the header and type "sh filename"
if `test ! -s ./pgppad.pl`
then
echo "writing ./pgppad.pl"
cat > ./pgppad.pl << '\End\Of\Shar\'
# Perl module to allow padding and some other manipulation of PGP
# files.
#
# Include this with the statement:
# require 'pgppad.pl'
#
# 10/16/93
# Hal Finney


# Read a PGP Cipher Type Byte and the following length.
# One argument: file to read from
# Returns several things, in this order:
# CTB, with the length information removed, as a number.
# Length of following packet.
# Name of this kind of packet, made up, see list below.
# Packed CTB/length packet, suitable for writing out.
# Returns an empty string on error.
sub read_ctb {
    local($file) = @_;
    local($ctb, $length, $name, $rctb, $rlength, $lengthlength);

    if (read ($file, $rctb, 1) != 1) {		# Raw ctb
	return "";
    }
    $ctb = unpack ("C", $rctb);
    if ($ctb < 128) {
	return "";		# Must have high bit set
    }
    $lengthlength = $ctb % 4;
    $ctb -= $lengthlength;
    if ($lengthlength == 0) {
	$lengthlength = 1;
    } elsif ($lengthlength == 1) {
	$lengthlength = 2;
    } elsif ($lengthlength == 2) {
	$lengthlength = 4;
    } else {
	$lengthlength = 0;
	$length = -1;	# Unknown length
    }
    if (read ($file, $rlength, $lengthlength) != $lengthlength) {
	return "";
    }
    if ($lengthlength==1) {
	$length = unpack("C", $rlength);
    } elsif ($lengthlength==2) {
	$length = unpack("n", $rlength);
    } elsif ($lengthlength==4) {
	$length = unpack("N", $rlength);
    }
    $rctb = pack ("C a".$lengthlength, $rctb, $rlength);  # Packed data
    if ($ctb==0x84) {
	$name = "pubkey header";
    } elsif ($ctb==0x88) {
	$name = "signature";
    } elsif ($ctb==0x8c) {
	$name = "message digest";
    } elsif ($ctb==0x94) {
	$name = "secret key";
    } elsif ($ctb==0x98) {
	$name = "public key";
    } elsif ($ctb==0xa0) {
	$name = "compressed";
    } elsif ($ctb==0xa4) {
	$name = "conventional encrypted";
    } elsif ($ctb==0xa8) {
	$name = "plaintext";
    } elsif ($ctb==0xb0) {
	$name = "trust";
    } elsif ($ctb==0xb4) {
	$name = "user id";
    } elsif ($ctb==0xb8) {
	$name = "comment";
    } else {
	return "";
    }
    return ($ctb, $length, $name, $rctb);
}

# Write a CTB and length field out.
# 3 arguments: file handle, ctb value, and length in bytes.
# No return value.
# Length gets output as 1, 2, or 4 bytes, the smallest in which it
# will fit.
# If length is negative we output no length field, but an "indefinite
# length" code is added to ctb.
sub write_ctb {
    local($file, $ctb, $length) = @_;
    local($rctb);

    $ctb = $ctb - ($ctb % 4);	# Be sure 2 low bits are clear
    if ($length < 0) {
	$rctb = pack ("C", $ctb+3);		# Packed data
    } elsif ($length > 65535) {
	$rctb = pack ("C N", $ctb+2, $length);  # Packed data
    } elsif ($length > 255) {
	$rctb = pack ("C n", $ctb+1, $length);  # Packed data
    } else {
	$rctb = pack ("C C", $ctb+0, $length);  # Packed data
    }
    print $file $rctb;
}

# This entry point always outputs a 4-byte count.  Length must be > 0.
# Otherwise like write_ctb.
sub write_ctb_4 {
    local($file, $ctb, $length) = @_;
    local($rctb);

    $ctb = $ctb - ($ctb % 4);	# Be sure 2 low bits are clear
    if ($length < 0) {
	die ("write_ctb_4 called with negative length\n");
    }
    $rctb = pack ("C N", $ctb+2, $length);  # Packed data
    print $file $rctb;
}


# Pad a PGP public-key-encrypted file to the specified length.
# Arguments: input file handle; output file handle; new size.
# Returns negative value on error.  See the code for what the
# different values mean.
# Returns 0 on success.
sub pgppad {
    local($infile, $outfile, $size) = @_;
    local($ctb, $length, $name, $rctb, $insize, $buf);

    # Read ctb & length of pubkey header
    ($ctb, $len, $name, $rctb) = &read_ctb($infile);
    if ($ctb == 0) {
	return -1;	# Error
    }
    if ($name ne "pubkey header") {
	return -2;	# Error
    }
    if ($len < 0) {
	return -3;	# Error
    }

    $insize = length($rctb) + $len;

    # Read packet of pubkey header
    if (read ($infile, $data, $len) != $len) {
	return -3;
    }

    # Write out pubkey header, unchanged
    &write_ctb($outfile, $ctb, $len);
    print $outfile $data;

    # Read ctb and length of conventional packet
    ($ctb, $len, $name, $rctb) = &read_ctb($infile);
    if ($ctb == 0) {
	return -4;	# Error
    }
    if ($name ne "conventional encrypted") {
	return -5;	# Error
    }

    # Calculate size of outgoing conventional packet.
    # Assume rctb won't change size; it may grow by 1 or 2 in some
    # rather rare cases, in which case we'll be a byte or two too big.
    $size -= $insize + length($rctb);
    if ($size < $len) {
	return -6;	# Error
    }

    # Output CTB with new length
    &write_ctb_4($outfile, $ctb, $size);

    # Copy remainder of input file
    while (read ($infile, $buf, 32768)) {
	print $outfile $buf;
    }

    # Note that this random number generator is probably not
    # cryptographically strong.
    srand (time|$$);
    while ($len < $size) {
	print $outfile pack ("C", int(rand(256)));
	++$len;
    }

    return 0;		# Success
}

1;	# Non-zero return for 'require'
\End\Of\Shar\
else
  echo "will not over write ./pgppad.pl"
fi
if `test ! -s ./pgppadt.pl`
then
echo "writing ./pgppadt.pl"
cat > ./pgppadt.pl << '\End\Of\Shar\'
# Test program for pgppad.pl, showing how to use it.
require 'pgppad.pl';

open (IN, $ARGV[0]) || die ("Couldn't open $ARGV[0]\n");
open (OUT, ">$ARGV[0].pad") || die ("Couldn't create $ARGV[0].pad\n");

$padding = $ARGV[1];

@stat = stat(IN);
$size = $stat[7];
print "Input file $ARGV[0] has size $size bytes\n";
print "Output file $ARGV[0].pad will have size ".$size+$padding." bytes\n";

if (($code = &pgppad (IN, OUT, $size+$padding)) < 0) {
    die ("pgppad returns code $code\n");
}

close (IN);
close (OUT);
print ("Done\n");

\End\Of\Shar\
else
  echo "will not over write ./pgppadt.pl"
fi
echo "Finished archive 1 of 1"
exit





Thread