Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Implementing Your Own Cryptographic Provider Using the Java Cryptography Extension - Meyers R.K., Frank C. E

..pdf
Скачиваний:
20
Добавлен:
24.05.2014
Размер:
239.52 Кб
Скачать

Implementing Your Own Cryptographic Provider

Using the Java Cryptography Extension

Russell K. Meyers

Northern Kentucky University

Highland Heights, KY 41099

Email: meyersru@nku.edu

ABSTRACT

This paper examines the process to implement a provider for a cryptographic method using the Java Cryptography Extension (JCE). We look at how the JCE operates, and the implementation of the El Gamal public key algorithm for encryption and decryption. We describe testing the provider with some troubleshooting hints.

1. INTRODUCTION

With the prominence of identity theft on the rise, we must all be wary of the security of online communication. One solution is to “hide” our data by using encryption algorithms that only allow those that we trust to view the information. There are several questions that we wanted to answer in completing this project:

1.How can we use Java to perform encryption of data, and what limitations are there?

2.Theoretically, encryption algorithms operate on numbers, but how can Java handle data that is not numeric in nature, i.e. bytes?

3.How does a program know where to find an algorithm?

4.When we implement a provider in Java, where in the file system is the best place to put it?

5.How do we encrypt large amounts of data from files?

The Java programming language includes the Java Cryptography Architecture (JCA) and Java Cryptography Extension (JCE), which include many algorithms to help us. The JCA and JCE also allow us to add our own algorithms, which, in the future, may be even more secure and efficient than those we have now. We will examine the JCA and JCE and show how to utilize them, including how to implement one’s own cryptographic provider using the El Gamal encryption algorithm. The fully commented code, along with documentation, can be found for download at www.nku.edu/~frank/ElGamal.

2. BACKGROUND

2.1 The JCA and JCE

The Java Cryptography Architecture (JCA) is included in the Java 2 run-time environment distributed by Sun. It includes algorithms to perform message digests, create digital signatures, and generate key pairs. There are no algorithms to perform ciphers, which is due to the fact that when the JCA was first released, export restrictions would not allow Sun to distribute such algorithms. The

Charles E. Frank

Northern Kentucky University

Highland Heights, KY 41099

Email: frank@nku.edu

lack of cipher algorithms led to the release of the Java Cryptographic Extension (JCE), which includes the encryption and decryption cipher algorithms. It also includes algorithms to generate single keys and secret keys.

In order to create an instance of a class, one does not use the new operator as is normally true with objects. The JCA and JCE use the factory pattern, which is a “pattern that defines an interface for creating a object, but lets the subclasses decide which class to actually instantiate.” [1]

Here is an example of such usage:

KeyPairGenerator kpg = KeyPairGenerator.getInstance(“ElGamal,

”Meyers”);

This is done so that it is easier to change which algorithm someone might want to use (the first parameter) by simply altering a string, rather than having to change a line of code with a new statement. The second parameter is the provider name. Every algorithm must be associated with a provider, and multiple providers can support any single given algorithm.

It is unfortunate, however, that Sun’s JCE does not support as many algorithms that could be useful for us as we would like. Therefore, the JCE that we have used is one provided by BouncyCastle [4]. We found that this version of the JCE is the most complete available that is free for download, and is the easiest to add our own provider.

In order to gain access to BouncyCastle’s JCE, it was necessary to disable Sun’s version first. For a Windows machine with Borland’s JBuilder 8, there were two directories that needed to be accessed. The first was the Java Runtime Environment in the JBuilder directory, which is:

C:\JBuilder8\jdk1.4\jre\lib(\ext)

This represents the Software Development Kit (SDK) for the machine. We needed to delete the jce.jar file from the lib directory, and the SunJCE_provider.jar file from the ext directory. We replaced these two files with the Bouncy Castle file, jce-jdk13-119.jar in both locations.

The same process was repeated for the Java 2 Runtime environment, which is located at:

C:\Program

Files\JavaSoft\JRE\1.4\lib(\ext)

There is one more step to complete in order to be able to use the BouncyCastle JCE with provider. We needed to

add a line to the java.security file, located in the \security directory, parallel to \ext in both cases above. We added the following line to the list of providers:

security.provider.6=org.bouncycastle.

jce.provider.BouncyCastleProvider

This process gave us access to a full JCE, which we could easily extend with our own provider.

2.2 The El Gamal Algorithm

The algorithm that we implemented is the El Gamal encryption algorithm [5]. It is a public key algorithm, which utilizes modular arithmetic on large numbers. Let us start by examining what values constitute both the public and private keys, and how to generate them. The first step is to generate a large prime number, called p, which will be our modulus. Then, we generate another large number, α, such that α is less than p, and relatively prime to p. The next step is to generate a secret large number, called A, which will be the main element of the private key. The number A must be contained in the interval [1, p – 2]. Finally, we compute the last large number:

y =αA modp

The public key consists of p, α, and y, while the private key consists of A and p. In order to encrypt a message M, we first generate yet another large number k, such that k is in the interval [1, p – 2]. There are two values that must be computed that will represent our cipher text, C. The first is γ, which is found by computing α to the k power, mod p:

γ =αk mod p

The second value, δ, is found by computing M multiplied by y to the k power, mod p:

δ =(M * yk ) mod p

So, our cipher text has become C = {δ, γ}. We can decrypt in a single step, by computing:

M = (δ *γA) mod p

We will look at the more specific details of how one might utilize the Java programming language to efficiently implement this algorithm. We will also look at how to connect it with the JCA and JCE, ways to test and use such an implementation. Finally we will present some problems that might arise during the process, and some troubleshooting hints.

3. IMPLEMENTATION PRELIMINARIES

Every class we implement must be in the same package. In our implementation, we have created our own package named Meyers. There are methods, data, and constructors in our code that we do not want classes in other packages to access. This will help keep the integrity of our code and algorithm established. For example, we do not want our test file to be able to access directly all of the core data structures that we use. All of our classes will be in separate files, so that we can keep our work organized and understandable.

We will be using the java.math.BigInteger class for our numeric values. It contains methods to perform the numeric computations we need, such as multiply, mod, and modPow. We do not know the efficiency of these algorithms, but in our testing we noticed no significant problems in speed or correctness.

The JCE includes a set of abstract classes called Service Provider Interfaces (SPI’s). We will need to extend these classes when implementing our provider so that we follow the framework of the JCE. We must also provide implementations for all of the methods defined in these SPI classes.

4. GETTING STARTED –

KEY IMPLEMENTATIONS 4.1 The El Gamal Public Key

Our El Gamal public keys are represented by a class named ElGamalPublicKey, which needs to implement the PublicKey interface:

public class ElGamalPublicKey implements PublicKey

This class has three private data members: p, α, and y, explained in section 2.2:

private BigInteger modulus; private BigInteger publicBase; private BigInteger y;

We need a way to convert the key into a byte array, because someone may need to send the key over a socket. This is accomplished by the method getEncoded, which sequentially takes the byte length and the byte array representation of each data member, and adds it to a byte stream. The stream is then converted into a byte array that is returned to the point of call.

4.2 The El Gamal Private Key

The implementation of our El Gamal private key is essentially the same as the public key:

public class ElGamalPrivateKey implements PrivateKey

One difference is that here we have only two private data members, A and p, as mentioned in section 2.2.

private BigInteger a; private BigInteger modulus;

The getEncoded method operates in the same way, except here there are only two data members to encode, so the encoded version will be somewhat shorter than the public key’s encoded version.

5. GENERATING KEY PAIRS

In section 2.2, we examined how to generate El Gamal key pairs mathematically.

In this section we will look at the practical ways to create these key pairs, using the key implementations introduced in section 4. We have created a class to do the work for us, named ElGamalKeyPairGenerator. This is the first occasion where we need to extend an SPI,

KeyPairGeneratorSpi. The class is declared final because we do not want anyone to extend this class and change how the keys are generated. This would be a serious security hole.

public final class ElGamalKeyPairGenerator extends KeyPairGeneratorSpi

The constant DEFAULT_LENGTH is the length of the modulus, but we decided to make all of the numbers the same size. It is important to mention here that there are two different meanings to the “length” of a number. In this class, the “length” of a number is the length in bits. We use this length in the constructor for the BigInteger class when generating the values. (The other meaning is the length in bytes, which will come later in our cipher.) The CERTAINTY constant is the probability of a prime used by the BigInteger constructor. For speed and efficiency reasons, we chose 85%, which most of the time will generate a probable prime in a rather quick fashion.

We needed a method to initialize our generator, called initialize, which takes the desired key size and a SecureRandom object as parameters. The second instance variable is of type SecureRandom, which we will use to generate random numbers. The significance of using a SecureRandom object opposed to a typical Random object is that a SecureRandom object is required to produce non-deterministic output. This method acts as a constructor typically would, because it assigns the instance variables to the passed in values, and turns on the initialization flag.

There is only one other method, generateKeyPair, and it is the one that does most of the work. It does not take any parameters, but it does return an object of type KeyPair. First we check to see if the generator has yet to be initialized, and if not, we go ahead and initialize it ourselves with default values:

if(!initialized) initialize(DEFAULT_LENGTH, new SecureRandom());

Next, we generate our modulus, p, given our desired length and “primeness” certainty of 85%:

p = new BigInteger(keyLength,CERTAINTY, secureRandom);

Now we need to find a value for α, which must be relatively prime to p. To do this, we create a while loop that will continually run and generate new α’s until a proper one has been found:

do

{

alpha = new BigInteger(keyLength, secureRandom);

}

while(!(p.gcd(alpha).equals(BigInteger. ONE)));

Then we do the same thing to find an A, except this time we want A to be between 1 and p – 2 inclusive:

do

{

a = new BigInteger(keyLength, secureRandom);

}

while(a.compareTo(pMinus2) > 0

|| a.compareTo(BigInteger.ONE) < 0);

The last computation we need is to find y, which we calculate, as appropriate from section 2.2, using the modPow method.

y = alpha.modPow(a, p);

Finally, we instantiated a new El Gamal Public and Private key, and return the new key pair. Now that we have generated our El Gamal key pair, we can develop a cipher to use them.

6. RAW ENCRYPTION USING NUMBERS

The most fundamental part of the implementation is the actual encryption of numeric data. These routines are the encryption and decryption primitives. We implemented these methods as static methods in class named ElGamal. We first needed a constant to define the length of any generated numbers. As with key generation, the “length” refers to the length in bits. There are only two methods, both of which are the primitives we need. The first method is named elgamalep, which takes two parameters, the public key we use to encrypt the message, and the message itself:

public static CipherTextPair elgamalep(ElGamalPublicKey publicKey,BigInteger message)

throws IllegalBlockSizeException

We throw an IllegalBlockSizeException if the message is negative or larger than the modulus. Because we are using Electronic Code Book (ECB) blocking style, we can force the blocks we read to be in this range. The return value is an object of type CipherTextPair, which we will explain later. We initialize a new SecureRandom object so we can generate a new

BigInteger:

SecureRandom secureRandom = new

SecureRandom();

This is used to generate a new value, k, to use in the encryption. Because this value must fall in the range between 1 and p – 2 inclusive, we create a loop to continually generate new k’s until one is found:

do

{

k = new BigInteger(DEFAULT_LENGTH, secureRandom);

}

while((k.compareTo(BigInteger.ONE) < 0

|| k.compareTo(modulusMinus2) > 0));

Since the cipher text consists of two values, δ and γ, we must compute these values using the modPow method:

BigInteger gamma = publicBase.modPow(k,modulus);

BigInteger delta = (message.multiply(y.modPow(k, modulus))).mod(modulus);

We are only allowed to return a single value, so we must wrap the two values in a class, called CipherTextPair. The only data members the class contains are of type BigInteger for δ and γ. There is a simple constructor to assign these data members the appropriate values, and two accessor methods to allow us to get the values we need. We can decrypt a message in the other method called elgamaldp. It takes two parameters, similarly to elgamalep; one for the private key, and the other of type CipherTextPair representing the cipher text:

public static BigInteger elgamaldp(ElGamalPrivateKey privateKey, CipherTextPair ciphertext)

This method is much simpler because there is only one mathematical step. Because this step is somewhat complex, we broke it down into two computations. The first step is to compute the value inverted:

BigInteger inverted = gamma.modPow(a.negate(), modulus);

We can use this value to find the message:

BigInteger plaintext = (delta.multiply(inverted)).mod( modulus);

7. PADDING

7.1 Why Pad Messages?

There are several reasons as to why messages are padded. The first is likely the most important: security. Since security is the reason we have cryptography, it only makes sense to use padding to our advantage. It helps us by “camouflaging” the data inside of the encryption, which means that it adds random bytes to our data so it is more difficult for a prospective attacker to find the actual data. Padding also helps us by putting our data in blocks, so we can operate on pieces of data that are the same size. Finally, it provides a standard way to block our data so that we can transport it to other users in a standard form. We have implemented two types of padding in our provider, PKCSv1_5 and OAEP (Optional Asymmetric Encryption Padding). We should mention that the code for the padding implementations was written by Jess Garms and Daniel Somerfield, the authors of Java Professional Security [4]. Our intention is to explain how the padding works and allow you to examine the code. The implementation uses the Padding interface, which can be found in the code. This interface allows either padding method to be used at run-time.

7.2 PKCS1v1_5 Padding

This is the simpler and more widely used of the two types of padding mentioned. However, it is less secure than OAEP because it tends to repeat bytes of data in patterns. The encode method takes two parameters, the message and the desired length of the padded message. A padded message of this type must be at least ten bytes longer than the message itself. The first byte of the padded message must be a 2, which denotes that this block is an encrypted block. Immediately following this byte is a sequence of non-zero random bytes that must be at least eight bytes long. Then there must be a 0, which signifies the end of the padding and the beginning of the message. The role of the desired length is to find the length of the array of non-zero bytes. The algorithm takes the desired length, and subtracts the length of the message, then subtracts two to account for the extra two bytes we must add. Decoding will search through the array to find the first zero byte, and then throw away the padding to get the original data.

7.3 OAEP Padding

The OAEP algorithm is much more complicated than the PKCS version. The reason is that it masks the message using the random data. This masking is done using the XOR operator. There is an additional class that the OAEP algorithm uses in our implementation, called MGF, which stands for Mask Generation Function. We will not attempt to explain this class, as it is not inherent to learning how to implement a provider. The first step of the OAEP algorithm is to concatenate series of bytes. This consists of a hashing of algorithm parameters sent to the function, an array of zero bytes, a byte of value 1, and the message itself. This is called the Data Block (DB). We then mask the concatenated byte array by using the MGF, and then XOR it with the DB, and call it maskedDB. This is then sent to the MGF again, and XORed with the seed (a random octet of bytes the same length of the hashing function), and called maskedSeed. The padded message is then a concatenation of the maskedSeed followed by maskedDB. Decoding is virtually the opposite process to encoding.

8. ENCRYPTION AND DECRYPTION IMPLEMENTATION - CIPHER

8.1 Constants and Instance Variables

The class that is the most important to the functionality of our provider is called ElGamalCipher. In this section we will look at some of the initial parts of the ElGamalCipher class that we needed to implement. Here is the class declaration:

public class ElGamalCipher extends CipherSpi

There are only two constants defined, and they are meant to differentiate between the two types of padding we have implemented:

private static final int PKCS1 = 1; private static final int OAEP = 2;

There are seven instance variables, with the first couple being an integer representing the padding type, and an instance of the appropriate padding class:

private int paddingType; private Padding myPadding;

The value of the paddingType can only be one of the two constants previously mentioned. The next two instance variables are the key and the key size. It is only possible to encrypt or decrypt, at any given time, so we will only use one type of key, public or private. The key size is typically the size of the modulus in bytes:

private Key myKey; private int keySize;

The operating mode can be one of two integer values,

Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE:

private int operationMode;

The final two variables are a byte stream to collect the byte arrays to return from different methods, and an instance of the AlgorithmParameters class.

Algorithm parameters are only used for OAEP padding, but even there they are optional:

private ByteArrayOutputStream stream; private AlgorithmParameters parameters;

8.2 Beginning Methods and Constructor

Since we have extended an SPI class, there are multiple methods that we must define. The default constructor calls the CipherSpi constructor, and initializes the byte stream. This should never be directly called, because this class uses the factory pattern.

public ElGamalCipher()

{

super();

stream = new ByteArrayOutputStream();

}

The method engineSetMode sets the mode of the cipher. We only support Electronic Code Book (ECB) mode, so we throw an exception if the user attempts to set the mode to something else. This, the mode of the cipher, is not to be confused with the operation mode, which is encryption or decryption.

protected void engineSetMode(String mode) throws

NoSuchAlgorithmException

{

if(!mode.equalsIgnoreCase("ECB")) throw new

NoSuchAlgorithmException("ElGamal only supports ECB mode");

}

We also have a method to set the padding type. Because we only support PKCS1v1_5 padding and OAEP, we throw an exception if something else is entered.

protected void engineSetPadding(String padding) throws

NoSuchPaddingException

{

if(padding.equalsIgnoreCase("PKCS1")

||padding.equalsIgnoreCase(

"PKCS#1")

||padding.equalsIgnoreCase(

"PKCS1Padding"))

{

paddingType = PKCS1;

}

else if(padding.equalsIgnoreCase("OAEP") || padding.equalsIgnoreCase(

"OAEPPadding"))

{

paddingType = OAEP;

}

else

{

throw new NoSuchPaddingException( "Only PKCS#1 and OAEP padding are supported");

}

}

The next two methods deal with the block size. The method engineGetBlockSize returns the block size that would be input to the cipher, while engineGetOutputSize returns the size of the block from the output of the cipher, depending on the operation mode. The input for encryption and output for decryption should be the size of the key in bytes. The output for encryption and input for decryption should be twice the size of the key plus two, because there are two values in the cipher text and two extra bytes to store the lengths of those values:

protected int engineGetBlockSize()

{

if(operationMode == Cipher.ENCRYPT_MODE)

return keySize; else

return (2 * keySize) + 2;

}

protected int engineGetOutputSize(int inputLength)

{

if(operationMode == Cipher.ENCRYPT_MODE)

return (2 * keySize) + 2; else

return keySize;

}

8.3 Initialization of the Cipher

There is a special method to initialize the cipher, called engineInit. There are three of these methods, each taking a different set of parameters. We implemented the majority of the method in the most commonly called version. The first version is the most functional:

protected void engineInit(int mode, Key key, AlgorithmParameters

params, SecureRandom random) throws InvalidKeyException,

InvalidAlgorithmParameterException

The first two parameters are the operation mode (either encryption or decryption), and the appropriate key for this mode. We check to see if the key and the mode match, and if not, we throw an InvalidKeyException:

int modulusLength = 0;

if(key instanceof ElGamalPublicKey)

{

if(!(mode == Cipher.ENCRYPT_MODE)) throw new

InvalidKeyException("Public keys can only be used for encryption");

modulusLength = ((ElGamalPublicKey)key).

getModulus().bitLength();

}

else if(key instanceof ElGamalPrivateKey)

{

if(!(mode == Cipher.DECRYPT_MODE)) throw new

InvalidKeyException("Private keys can only be used for decryption");

modulusLength = ((ElGamalPrivateKey)key).

getModulus().bitLength();

}

else

{

throw new InvalidKeyException("Key must be an El Gamal key");

}

Once we have the length of the modulus in bits, we can convert it to bytes by rounding up to the next byte to get the key size:

keySize = (modulusLength + 7) / 8;

We can then initialize our padding instance, depending on the type chosen:

if(paddingType == OAEP)

{

if(params == null) myPadding = new

OAEPPadding(random);

else

myPadding = new OAEPPadding(params, random);

}

else

myPadding = new PKCS1Padding(random);

Finally, we set the rest of our instance variables:

parameters = params; myKey = key; operationMode = mode; stream.reset();

The second version of the engineInit method takes the same parameters as the first version, excluding the AlgorithmParameters. We call the first version of engineInit, with the third parameter being null:

protected void engineInit(int mode, Key key, SecureRandom random)

throws InvalidKeyException

{

try

{

engineInit(mode, key, (AlgorithmParameters)null, random);

}

catch(InvalidAlgorithmParameter Exception iape)

{

iape.printStackTrace();

}

}

The third and final version of the engineInit method is only there so we can compile. We do not support

AlgorithmParameterSpec objects, so we throw an exception if someone attempts to call it.

protected void engineInit(int mode, Key key, java.security.spec. AlgorithmParameterSpec params, SecureRandom random)

throws InvalidAlgorithmParameterException

{

throw new InvalidAlgorithmParameterException(

"This cipher does not accept AlgorithmParameterSpec");

}

8.4 Updating and Finishing the Cipher

Updating a cipher is a simple process using the engineUpdate method. There are two versions, where one has more functionality. The only difference between them is that there are a different set of parameters and a different return type. The first version of engineUpdate takes a byte array to add to the byte stream, and an offset and length for that array. A simple call to the write method of the byte stream is all that is needed:

protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLength)

{

if(input != null) stream.write(input, inputOffset,

inputLength); return null;

}

The return value is null because we would be returning the resulting encrypted or decrypted byte array, but the cipher is not finished until we call engineDoFinal.

The second version has two parameters for the finished byte array, but again we would not be finished at this point. So, we call the first engineUpdate, returning zero for the same reason we returned null above:

protected int engineUpdate(byte[] input, int inputOffset,

int inputLength, byte[] output, int outputOffset)

{

engineUpdate(input, inputOffset, inputLength);

return 0;

}

The cipher is completed, in the user’s perspective, when the method engineDoFinal is called. The engineDoFinal methods closely mirror the engineUpdate methods in their parameter sets and return values. The first version takes the same three input values as the first version of engineUpdate:

protected byte[] engineDoFinal(byte[] input, int inputOffset,

int inputLength)

throws IllegalBlockSizeException, BadPaddingException

The first operation of this method is to call the engineUpdate method to “fill” the byte stream:

engineUpdate(input, inputOffset, inputLength);

byte[] output = new byte[stream.size()];

A byte array, called output, holds the data that is to be returned to the user. We call the encrypt inner method or decrypt inner method, depending on operating mode. These methods will be introduced shortly:

if(operationMode == Cipher.ENCRYPT_MODE)

{

try

{

output = encrypt();

}

catch (IOException ioe)

{

System.err.println("Encryption IO problem");

ioe.printStackTrace();

}

}

else

output = decrypt();

Finally, we clear the byte stream so that we can perform a new cipher on a different set of data.

The other version of engineDoFinal has the same parameter set as the second version of engineUpdate, and also returns an integer:

protected int engineDoFinal(byte[] input, int inputOffset,

int inputLength, byte[] output, int outputOffset)

throws ShortBufferException, IllegalBlockSizeException, BadPaddingException

This method creates a byte array, buffer, and call the first version of engineDoFinal to put the completed array in buffer:

byte[] buffer;

buffer = engineDoFinal(input, inputOffset, inputLength);

We check to see if buffer will fit into the output array specified. If it is not too long, we copy buffer into the output array, otherwise throwing an exception. We finish by returning the length of buffer, which is the total number of output bytes:

if(output.length - outputOffset < buffer.length)

{

throw new ShortBufferException(

"Output is too long for the buffer");

}

System.arraycopy(buffer, 0, output, outputOffset, buffer.length);

return buffer.length;

8.5 Internal Encrypt and Decrypt Methods

There are internal methods for encryption and decryption, named encrypt and decrypt. These are the methods that do the conversions between byte arrays and BigInteger variables, and call our encryption primitives. The encrypt method creates a byte array from the byte stream, encodes it with the proper padding, and then creates a new BigInteger:

byte[] M = stream.toByteArray(); byte[] paddedMessage =

myPadding.encode(M, keySize); BigInteger numericMessage = new

BigInteger(1, paddedMessage);

We can then use the elgamalep method to encrypt the data:

CipherTextPair numericCipherText = ElGamal.elgamalep((ElGamalPublic

Key)myKey, numericMessage);

We need to convert the two values of the CipherTextPair into a byte array. The Util class has a method called I2OSP to do this for us, but we will not explain this code. First, we compute the length (in bytes) of these two values, and initialize byte arrays of these sizes:

int deltaLength = (numericCipherText.getDelta().

bitLength() + 7) / 8; int gammaLength =

(numericCipherText.getGamma(). bitLength() + 7) / 8;

byte[] deltaBytes = new byte[deltaLength]; byte[] gammaBytes = new byte[gammaLength];

We check to see that both values of the CipherTextPair are the proper size, and if not, we add as many zero bytes to the front of the number as necessary and adjust the length variables. In either case, we use the I2OSP method to do the conversions:

deltaBytes = Util.I2OSP(numericCipherText. getDelta(), deltaLength);

gammaBytes = Util.I2OSP(numericCipherText. getGamma(), gammaLength);

We add these lengths and byte arrays to a byte stream, convert it to a byte array, and then return it:

ByteArrayOutputStream baos = new ByteArrayOutputStream();

. . .

baos.write(deltaLength);

baos.write(deltaBytes);

baos.write(gammaLength);

baos.write(gammaBytes);

byte[] C = baos.toByteArray();

return C;

The internal decrypt method takes the cipher text byte array and reads in the CipherTextPair values:

ByteArrayInputStream bais = new

ByteArrayInputStream(C);

int deltaOffset = bais.read(); byte[] d = new byte[deltaOffset]; bais.read(d, 0, deltaOffset);

int gammaOffset = bais.read(); byte[] g = new byte[gammaOffset]; bais.read(g, 0, gammaOffset);

We then can convert these values into BigInteger values, and create a new CipherTextPair:

BigInteger delta = new BigInteger(1,d); BigInteger gamma = new BigInteger(1,g); CipherTextPair ctp = new

CipherTextPair(delta, gamma);

After decrypting the data, we then convert it into a byte array, remove the padding, and return the message:

BigInteger numericMessage = ElGamal.elgamaldp((ElGamalPrivate Key)myKey, ctp);

byte[] paddedMessage = Util.I2OSP(numericMessage,

keySize);

byte[] M = myPadding.decode(paddedMessage);

return M;

9. WRAP UP – REGISTERING OUR PROVIDER

We have to connect the El Gamal algorithm to our provider and register it with the Java Virtual Machine by implementing a class named ElGamalProvider. This class is declared final so that we do not have to worry about others trying to extend this class:

public final class ElGamalProvider extends java.security.Provider

There are three constants declared, but the most important is the name of the provider, because this string is the one the user enters into the factory pattern getInstance method.

private static final String NAME = "Meyers";

The only method in this class is the constructor, where first call the parent class’s constructor:

super(NAME, VERSION, INFO);

We must associate the algorithm we support with the class that implements it. This is accomplished by calling the static method doPrivileged of the class AccessController. This is necessary because the code calling our algorithm may not have the privileges to do this, but we do. We need to create a new

PrivilegedAction to pass into the doPrivileged method:

AccessController.doPrivileged(new java.security.PrivilegedAction() {

public Object run()

{

put("Cipher.ElGamal",

"meyers.ElGamalCipher");

put("KeyPairGenerator.ElGamal",

"meyers.ElGamalKeyPairGenerator"); return null;

}

});

We crated a new anonymous inner class with a single method, run, which associates our algorithms appropriately using the put method.

10. COMPILING, TESTING AND TROUBLESHOOTING

We needed a class to test our new provider, so we created JCETest. It has the functionality to complete four separate tasks:

1.Generate a key pair and write them to two separate files.

2.Encrypt and promptly decrypt a given string. This is the primary test of our provider to make sure the pure algorithm functions properly.

3.Encrypt a file

4.Decrypt a file

The main method in JCETest first registers our provider as the primary provider in order to allow access to our algorithm. We allow the user to choose one of the four options mentioned above.

The method createKeyPair gets an instance of our KeyPairGenerator to create a key pair. It writes these keys to separate files, so that the user can specify which key he would like to use later:

public static void createKeyPair() throws Exception

{

. . .

KeyPairGenerator kpg = KeyPairGenerator.getInstance( "ElGamal","Meyers");

kpg.initialize(1024);

KeyPair keyPair = kpg.generateKeyPair();

. . .

}

The method encryptAndDecryptString was our primary testing method. We ask the user for a string and a key pair, then perform a cipher on the string twice. Note that we only need to get an instance of the cipher once, but we need to initialize it twice, once for encryption and a second time for decryption:

public static void encryptAndDecryptString() throws Exception

{

. . .

Cipher cipher = Cipher.getInstance("ElGamal/ECB", "Meyers");

cipher.init(Cipher.ENCRYPT_MODE, publicKey);

byte[] encryptedMessage = cipher.doFinal(messageBytes);

System.out.println("Encrypted

Message:");

. . .

cipher.init(Cipher.DECRYPT_MODE, privateKey);

byte[] decryptedMessage = cipher.doFinal(encryptedMessage);

. . .

}

The last two methods to encrypt and decrypt a file are similar. Because the maximum block size we are using is the key size (128 bytes), the maximum encryption block we can handle of pure data is 118 bytes:

public static void encryptFile() throws Exception

{

. . .

Cipher cipher = Cipher.getInstance("ElGamal/ECB/

PKCS1Padding", "Meyers"); cipher.init(Cipher.ENCRYPT_MODE,

publicKey);

. . .

byte[] buffer = new byte[118]; byte[] output;

int length = in.read(buffer); while(length != -1)

{

output = cipher.doFinal(buffer); out.write(output,0,output.length); clear(buffer);

length = in.read(buffer);

}

. . .

}

In decrypting a file, we know the block size will be 258 bytes, which is equivalent to (2 * 128) + 2, as stated in section 8.2.

public static void decryptFile() throws Exception

{

Cipher cipher = Cipher.getInstance("ElGamal/ECB/ PKCS1Padding", "Meyers");

cipher.init(Cipher.DECRYPT_MODE, privateKey);

. . .

byte[] buffer = new byte[258]; byte[] output;

int length = in.read(buffer); while(length != -1)

{

output = cipher.doFinal(buffer); out.write(output,0,output.length); clear(buffer);

length = in.read(buffer);

}

. . .

}

In order to compile our classes, we created a new JBuilder project, and placed all of the source files in the src directory if the project. There are some trouble spots that we encountered, and others that could also arise.

If you are having difficulty compiling the classes, and you see an error stating that the compiler cannot access java/security or javax/crypto, you should make sure that you have a JCE installed properly. During run-time, if you encounter the exception “Cannot set up certs for trusted CAs” or “The provider <provider name> may not be signed by a trusted party”, you probably still have Sun’s JCE active. This was one of the major problems we encountered, and the only way to fix it was to delete Sun’s JCE, as they require that all providers be signed by Sun in order to be used with their JCE. We thought that getting our provider signed might be an option, but as Garms and Somerfield write, “Our experience indicates that, if you find yourself in a situation requiring a signed provider, you should allow plenty of time for the process. Our experience is that at least four to six months may be required, as you’ll need to deal with Sun and the U.S. Bureau of Export Control.” [1]

11. CONCLUSION

We have shown the steps involved in using the JCA and JCE, and also how to implement your own cryptographic provider that you can customize to your needs. The JCE is a powerful tool, and because of its flexibility, it will continue to grow into an invaluable part of online communication, business, and eCommerce. As the research in cryptography continues to thrive, the hope is that new algorithms will be developed that are even stronger and more secure than those of today. These new algorithms can easily be added to the JCE so that everyone can access them in a useful manner. Finally, I would like to acknowledge the Center for Integrated Science and Mathematics (CINSAM) of Northern Kentucky University for providing support for this project. It was a great learning experience that I will find very helpful in my pursuit of future graduate study in computer security and cryptography.

12. REFERENCES

[1]Garms, Jess, and Somerfield, Daniel, Professional Java Security, Wrox Press, 2001.

[2]Kaufman, Perlman, and Speciner, Network Security: Private Communication in a Public World, Prentice Hall, 2002.

[3]Cornell, Gary and Horstmann, Cay S., Core Java: Volume II – Advanced Features, Sun Microsystems, 2002.

[4]BouncyCastle, http://www.bouncycastle.org.

[5]Menezes, A., van Oorschot, P., and Vanstone, S., Handbook of Applied Cryptography, CRC Press, 1997.