KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > derby > impl > drda > DecryptionManager


1 /*
2
3    Derby - Class org.apache.derby.impl.drda.DecryptionManager
4
5    Licensed to the Apache Software Foundation (ASF) under one or more
6    contributor license agreements. See the NOTICE file distributed with
7    this work for additional information regarding copyright ownership.
8    The ASF licenses this file to You under the Apache License, Version 2.0
9    (the "License"); you may not use this file except in compliance with
10    the License. You may obtain a copy of the License at
11
12       http://www.apache.org/licenses/LICENSE-2.0
13
14    Unless required by applicable law or agreed to in writing, software
15    distributed under the License is distributed on an "AS IS" BASIS,
16    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17    See the License for the specific language governing permissions and
18    limitations under the License.
19
20  */

21
22 package org.apache.derby.impl.drda;
23
24 import java.security.KeyPairGenerator JavaDoc;
25 import java.security.KeyPair JavaDoc;
26 import javax.crypto.KeyAgreement;
27 import javax.crypto.spec.DHParameterSpec;
28 import javax.crypto.interfaces.DHPublicKey;
29 import javax.crypto.spec.DHPublicKeySpec;
30 import javax.crypto.spec.SecretKeySpec;
31 import javax.crypto.Cipher;
32 import javax.crypto.spec.IvParameterSpec;
33 import java.security.spec.AlgorithmParameterSpec JavaDoc;
34 import java.security.KeyFactory JavaDoc;
35 import java.security.PublicKey JavaDoc;
36 import java.sql.SQLException JavaDoc;
37 import java.math.BigInteger JavaDoc;
38 import org.apache.derby.shared.common.sanity.SanityManager;
39
40 /**
41  * This class is used to decrypt password and/or userid.
42  * It uses Diffie_Hellman algorithm to get the publick key and secret key, and then
43  * DES encryption is done using certain token (based on security mechanism) and
44  * this side's own public key. Basically, this class is called when using a security
45  * mechanism that encrypts user ID and password (eusridpwd). This class uses IBM JCE
46  * to do Diffie_Hellman algorithm and DES encryption.
47  */

48
49 class DecryptionManager
50 {
51   // DRDA's Diffie-Hellman agreed public value: prime.
52
private static final byte modulusBytes__[] = {
53     (byte)0xC6, (byte)0x21, (byte)0x12, (byte)0xD7,
54     (byte)0x3E, (byte)0xE6, (byte)0x13, (byte)0xF0,
55     (byte)0x94, (byte)0x7A, (byte)0xB3, (byte)0x1F,
56     (byte)0x0F, (byte)0x68, (byte)0x46, (byte)0xA1,
57     (byte)0xBF, (byte)0xF5, (byte)0xB3, (byte)0xA4,
58     (byte)0xCA, (byte)0x0D, (byte)0x60, (byte)0xBC,
59     (byte)0x1E, (byte)0x4C, (byte)0x7A, (byte)0x0D,
60     (byte)0x8C, (byte)0x16, (byte)0xB3, (byte)0xE3
61   };
62
63   //the prime value in BigInteger form. It has to be in BigInteger form because this
64
//is the form used in JCE library.
65
private static final BigInteger JavaDoc modulus__
66   = new BigInteger JavaDoc (1, modulusBytes__);
67
68   // DRDA's Diffie-Hellman agreed public value: base.
69
private static final byte baseBytes__[] = {
70     (byte)0x46, (byte)0x90, (byte)0xFA, (byte)0x1F,
71     (byte)0x7B, (byte)0x9E, (byte)0x1D, (byte)0x44,
72     (byte)0x42, (byte)0xC8, (byte)0x6C, (byte)0x91,
73     (byte)0x14, (byte)0x60, (byte)0x3F, (byte)0xDE,
74     (byte)0xCF, (byte)0x07, (byte)0x1E, (byte)0xDC,
75     (byte)0xEC, (byte)0x5F, (byte)0x62, (byte)0x6E,
76     (byte)0x21, (byte)0xE2, (byte)0x56, (byte)0xAE,
77     (byte)0xD9, (byte)0xEA, (byte)0x34, (byte)0xE4
78   };
79
80   // The base value in BigInteger form. It has to be in BigInteger form because
81
//this is the form used in IBM JCE library.
82
private static final BigInteger JavaDoc base__ =
83     new BigInteger JavaDoc (1, baseBytes__);
84
85   //DRDA's Diffie-Hellman agreed exponential length
86
private static final int exponential_length__ = 255;
87
88   private KeyPairGenerator JavaDoc keyPairGenerator_;
89   private KeyPair JavaDoc keyPair_;
90   private KeyAgreement keyAgreement_;
91   private DHParameterSpec paramSpec_;
92
93   // Random Number Generator (PRNG) Algorithm
94
private final static String JavaDoc SHA_1_PRNG_ALGORITHM = "SHA1PRNG";
95   private final static int SECMEC_USRSSBPWD_SEED_LEN = 8; // Seed length
96

97   /**
98    * EncryptionManager constructor. In this constructor,DHParameterSpec,
99    * KeyPairGenerator, KeyPair, and KeyAgreement are initialized.
100    *
101    * @throws SQLException that wraps any error
102    */

103   DecryptionManager () throws SQLException JavaDoc
104   {
105     try {
106       if (java.security.Security.getProvider ("IBMJCE") == null) // IBMJCE is not installed, install it.
107
java.security.Security.addProvider ((java.security.Provider JavaDoc) Class.forName("IBMJCE").newInstance());
108       paramSpec_ = new DHParameterSpec (modulus__, base__, exponential_length__);
109       keyPairGenerator_ = KeyPairGenerator.getInstance ("DH", "IBMJCE");
110       keyPairGenerator_.initialize ((AlgorithmParameterSpec JavaDoc)paramSpec_);
111       keyPair_ = keyPairGenerator_.generateKeyPair();
112       keyAgreement_ = KeyAgreement.getInstance ("DH", "IBMJCE");
113       keyAgreement_.init (keyPair_.getPrivate());
114     }
115     catch (java.lang.ClassNotFoundException JavaDoc e) {
116       throw new SQLException JavaDoc ("java.lang.ClassNotFoundException is caught" +
117                               " when initializing EncryptionManager '" + e.getMessage() + "'");
118     }
119     catch (java.lang.IllegalAccessException JavaDoc e) {
120       throw new SQLException JavaDoc ("java.lang.IllegalAccessException is caught" +
121                               " when initializing EncryptionManager '" + e.getMessage() + "'");
122     }
123     catch (java.lang.InstantiationException JavaDoc e) {
124       throw new SQLException JavaDoc ("java.lang.InstantiationException is caught" +
125                               " when initializing EncryptionManager '" + e.getMessage() + "'");
126     }
127     catch (java.security.NoSuchProviderException JavaDoc e) {
128       throw new SQLException JavaDoc ("java.security.NoSuchProviderException is caught" +
129                               " when initializing EncryptionManager '" + e.getMessage() + "'");
130     }
131     catch (java.security.NoSuchAlgorithmException JavaDoc e) {
132       throw new SQLException JavaDoc ("java.security.NoSuchAlgorithmException is caught" +
133                   " when initializing EncryptionManager '" + e.getMessage() + "'");
134     }
135     catch (java.security.InvalidAlgorithmParameterException JavaDoc e) {
136       throw new SQLException JavaDoc ("java.security.InvalidAlgorithmParameterException is caught" +
137                   " when initializing EncryptionManager '" + e.getMessage() + "'");
138     }
139
140     catch (java.security.InvalidKeyException JavaDoc e) {
141       throw new SQLException JavaDoc ("java.security.InvalidKeyException is caught" +
142                   " when initializing EncryptionManager '" + e.getMessage() + "'");
143     }
144   }
145
146   /**
147    * This method generates the public key and returns it. This
148    * shared public key is the application server's connection key and will
149    * be exchanged with the application requester's connection key. This connection
150    * key will be put in the sectkn in ACCSECRD command and send to the application
151    * requester.
152    *
153    * @return a byte array that is the application server's public key
154    */

155   public byte[] obtainPublicKey ()
156   {
157     //The encoded public key
158
byte[] publicEnc = keyPair_.getPublic().getEncoded();
159
160     //we need to get the plain form public key because DRDA accepts plain form
161
//public key only.
162
BigInteger JavaDoc aPub = ((DHPublicKey) keyPair_.getPublic()).getY();
163     byte[] aPubKey = aPub.toByteArray();
164
165     //the following lines of code is to adjust the length of the key. PublicKey
166
//in JCE is in the form of BigInteger and it's a signed value. When tranformed
167
//to a Byte array form, normally this array is 32 bytes. However, if the
168
//value happens to take up all 32 X 8 bits and it is positive, an extra
169
//bit is needed and then a 33 byte array will be returned. Since DRDA can't
170
//recogize the 33 byte key, we check the length here, if the length is 33,
171
//we will just trim off the first byte (0) and get the rest of 32 bytes.
172
if (aPubKey.length == 33 && aPubKey[0]==0) {
173       byte[] newKey = new byte[32];
174       for (int i=0; i < newKey.length; i++)
175         newKey[i] = aPubKey[i+1];
176       return newKey;
177     }
178
179     //the following lines of code is to adjust the length of the key. Occasionally,
180
//the length of the public key is less than 32, the reason of this is that the 0 byte
181
//in the beginning is somehow not returned. So we check the length here, if the length
182
//is less than 32, we will pad 0 in the beginning to make the public key 32 bytes
183
if (aPubKey.length < 32) {
184       byte[] newKey = new byte[32];
185       int i;
186       for (i=0; i < 32-aPubKey.length; i++) {
187         newKey[i] = 0;
188       }
189       for (int j=i; j<newKey.length; j++)
190         newKey[j] = aPubKey[j-i];
191       return newKey;
192     }
193     return aPubKey;
194   }
195
196   /**
197    * This method is used to calculate the decryption token. DES encrypts the
198    * data using a token and the generated shared private key. The token used
199    * depends on the type of security mechanism being used:
200    * USRENCPWD - The userid is used as the token. The USRID is zero-padded to
201    * 8 bytes if less than 8 bytes or truncated to 8 bytes if greater than 8 bytes.
202    * EUSRIDPWD - The middle 8 bytes of the server's connection key is used as
203    * the token. Decryption needs to use exactly the same token as encryption.
204    *
205    * @param securityMechanism security mechanism
206    * @param initVector userid or server(this side)'s connection key
207    * @return byte[] the decryption token
208    */

209   private byte[] calculateDecryptionToken (int securityMechanism, byte[] initVector)
210   {
211     byte[] token = new byte[8];
212
213     //USRENCPWD, the userid is used as token
214
if (securityMechanism == 7) {
215       if (initVector.length < 8) { //shorter than 8 bytes, zero padded to 8 bytes
216
for (int i=0; i<initVector.length; i++)
217           token[i] = initVector[i];
218         for (int i=initVector.length; i<8; i++)
219          token[i] = 0;
220       }
221       else { //longer than 8 bytes, truncated to 8 bytes
222
for (int i=0; i<8; i++)
223           token[i] = initVector[i];
224       }
225     }
226     //EUSRIDPWD - The middle 8 bytes of the server's connection key is used as
227
//the token.
228
else if (securityMechanism == 9) {
229       for (int i = 0; i < 8; i++) {
230         token[i] = initVector[i + 12];
231       }
232     }
233     return token;
234   }
235
236   /**
237    * This method generates a secret key using the application requester's
238    * public key, and decrypts the usreid/password with the middle 8 bytes of
239    * the generated secret key and a decryption token. Then it returns the
240    * decrypted data in a byte array.
241    *
242    * @param cipherText The byte array form userid/password to decrypt.
243    * @param securityMechanism security mechanism
244    * @param initVector The byte array which is used to calculate the
245    * decryption token for initializing the cipher
246    * @param sourcePublicKey application requester (encrypter)'s public key.
247    * @return the decrypted data (plain text) in a byte array.
248    */

249   public byte[] decryptData (byte[] cipherText,
250                  int securityMechanism,
251                  byte[] initVector,
252                  byte[] sourcePublicKey) throws SQLException JavaDoc
253   {
254     byte[] plainText = null;
255     byte[] token = calculateDecryptionToken (securityMechanism, initVector);
256     try {
257
258       //initiate a Diffie_Hellman KeyFactory object.
259
KeyFactory JavaDoc keyFac = KeyFactory.getInstance ("DH", "IBMJCE");
260
261       //Use server's public key to initiate a DHPublicKeySpec and then use
262
//this DHPublicKeySpec to initiate a publicKey object
263
BigInteger JavaDoc publicKey = new BigInteger JavaDoc (1, sourcePublicKey);
264       DHPublicKeySpec dhKeySpec = new DHPublicKeySpec (publicKey, modulus__, base__);
265       PublicKey pubKey = keyFac.generatePublic (dhKeySpec);
266
267       //Execute the first phase of DH keyagreement protocal.
268
keyAgreement_.doPhase (pubKey, true);
269
270       //generate the shared secret key. The application requestor's shared secret
271
//key should be exactly the same as the application server's shared secret
272
//key
273
byte[] sharedSecret = keyAgreement_.generateSecret();
274       byte[] newKey = new byte[32];
275
276       //We adjust the length here. If the length of secret key is 33 and the first byte is 0,
277
//we trim off the frist byte. If the length of secret key is less than 32, we will
278
//pad 0 to the beginning of the byte array tho make the secret key 32 bytes.
279
if (sharedSecret.length == 33 && sharedSecret[0] == 0) {
280         for (int i=0; i<newKey.length; i++)
281           newKey[i] = sharedSecret[i+1];
282       }
283       if (sharedSecret.length < 32) {
284         int i;
285         for (i=0; i<(32 - sharedSecret.length); i++)
286             newKey[i] = 0;
287         for (int j=i; j<sharedSecret.length; j++)
288              newKey[j] = sharedSecret[j-i];
289       }
290
291       //The Data Encryption Standard (DES) is going to be used to encrypt userid
292
//and password. DES is a block cipher; it encrypts data in 64-bit blocks.
293
//DRDA encryption uses DES CBC mode as defined by the FIPS standard
294
//DES CBC requires an encryption key and an 8 byte token to encrypt the data.
295
//The middle 8 bytes of Diffie-Hellman shared private key is used as the
296
//encryption key. The following code retrieves middle 8 bytes of the shared
297
//private key.
298
byte[] key = new byte[8];
299
300       //if secret key is not 32, we will use the adjust length secret key
301
if (sharedSecret.length==32) {
302         for (int i=0; i< 8;i++)
303           key[i] = sharedSecret[i+12];
304       }
305       else if (sharedSecret.length==33 || sharedSecret.length < 32) {
306         for (int i=0; i< 8;i++)
307           key[i] = newKey[i+12];
308       }
309       else
310         throw new SQLException JavaDoc ("sharedSecret key length error " + sharedSecret.length);
311
312       // make parity bit right, even number of 1's
313
byte temp;
314       int changeParity;
315       for (int i=0; i<8; i++)
316       {
317         temp = key[i];
318         changeParity = 1;
319         for (int j=0; j<8; j++)
320         {
321             if (temp < 0)
322                 changeParity = 1 - changeParity;
323             temp = (byte) (temp << 1);
324         }
325         if (changeParity == 1)
326         {
327             if ((key[i] & 1) != 0)
328                 key[i] &= 0xfe;
329             else
330                 key[i] |= 1;
331         }
332       }
333
334       //use this encryption key to initiate a SecretKeySpec object
335
SecretKeySpec desKey = new SecretKeySpec (key, "DES");
336
337       //We use DES in CBC mode because this is the mode used in DRDA. The
338
//encryption mode has to be consistent for encryption and decryption.
339
//CBC mode requires an initialization vector(IV) parameter. In CBC mode
340
//we need to initialize the Cipher object with an IV, which can be supplied
341
// using the javax.crypto.spec.IvParameterSpec class.
342
Cipher cipher= Cipher.getInstance ("DES/CBC/PKCS5Padding", "IBMJCE");
343
344       //generate a IVParameterSpec object and use it to initiate the
345
//Cipher object.
346
IvParameterSpec ivParam = new IvParameterSpec (token);
347
348       //initiate the Cipher using encryption mode, encryption key and the
349
//IV parameter.
350
cipher.init (javax.crypto.Cipher.DECRYPT_MODE, desKey,ivParam);
351
352       //Execute the final phase of encryption
353
plainText = cipher.doFinal (cipherText);
354     }
355     catch (java.security.NoSuchProviderException JavaDoc e) {
356       throw new SQLException JavaDoc ("java.security.NoSuchProviderException is caught "
357                   + "when encrypting data '" + e.getMessage() + "'");
358     }
359     catch (java.security.NoSuchAlgorithmException JavaDoc e) {
360       throw new SQLException JavaDoc ("java.security.NoSuchAlgorithmException is caught "
361                   + "when encrypting data '" + e.getMessage() + "'");
362     }
363     catch (java.security.spec.InvalidKeySpecException JavaDoc e) {
364       throw new SQLException JavaDoc ("java.security.InvalidKeySpecException is caught "
365                   + "when encrypting data");
366     }
367     catch (java.security.InvalidKeyException JavaDoc e) {
368       throw new SQLException JavaDoc ("java.security.InvalidKeyException is caught "
369                   + "when encrypting data '" + e.getMessage() + "'");
370     }
371     catch (javax.crypto.NoSuchPaddingException e) {
372       throw new SQLException JavaDoc ("javax.crypto.NoSuchPaddingException is caught "
373                   + "when encrypting data '" + e.getMessage() + "'");
374     }
375     catch (javax.crypto.BadPaddingException e) {
376       throw new SQLException JavaDoc ("javax.crypto.BadPaddingException is caught "
377                   + "when encrypting data '" + e.getMessage() + "'");
378     }
379     catch (java.security.InvalidAlgorithmParameterException JavaDoc e) {
380       throw new SQLException JavaDoc ("java.security.InvalidAlgorithmParameterException is caught "
381                   + "when encrypting data '" + e.getMessage() + "'");
382     }
383     catch (javax.crypto.IllegalBlockSizeException e) {
384       throw new SQLException JavaDoc ("javax.crypto.IllegalBlockSizeException is caught "
385                   + "when encrypting data '" + e.getMessage() + "'");
386     }
387     return plainText;
388   }
389
390     /**
391      * This method generates an 8-Byte random seed.
392      *
393      * Required for the SECMEC_USRSSBPWD security mechanism
394      *
395      * @return a random 8-Byte seed.
396      */

397     protected static byte[] generateSeed() throws SQLException JavaDoc {
398         java.security.SecureRandom JavaDoc secureRandom = null;
399         try {
400           // We're verifying that we can instantiate a randon number
401
// generator (PRNG).
402
secureRandom =
403               java.security.SecureRandom.getInstance(SHA_1_PRNG_ALGORITHM);
404         } catch (java.security.NoSuchAlgorithmException JavaDoc nsae) {
405             throw new SQLException JavaDoc(
406                     "java.security.NoSuchAlgorithmException is caught" +
407                     " when initializing DecryptionManager '" +
408                     nsae.getMessage() + "'");
409         }
410         byte randomSeedBytes[] = new byte[SECMEC_USRSSBPWD_SEED_LEN];
411         secureRandom.setSeed(secureRandom.generateSeed(
412                                         SECMEC_USRSSBPWD_SEED_LEN));
413         secureRandom.nextBytes(randomSeedBytes);
414         // Return the 8-byte generated random seed
415
return randomSeedBytes;
416     }
417
418     /*********************************************************************
419      * RESOLVE: *
420      * The methods and static vars below should go into some 'shared' *
421      * package when the capability is put back in (StringUtil.java). *
422      *********************************************************************/

423
424     private static char[] hex_table = {
425                 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
426                 'a', 'b', 'c', 'd', 'e', 'f'
427             };
428     
429     /**
430         Convert a byte array to a String with a hexidecimal format.
431         The String may be converted back to a byte array using fromHexString.
432         <BR>
433         For each byte (b) two characaters are generated, the first character
434         represents the high nibble (4 bits) in hexidecimal (<code>b & 0xf0</code>),
435         the second character represents the low nibble (<code>b & 0x0f</code>).
436         <BR>
437         The byte at <code>data[offset]</code> is represented by the first two characters in the returned String.
438
439         @param data byte array
440         @param offset starting byte (zero based) to convert.
441         @param length number of bytes to convert.
442
443         @return the String (with hexidecimal format) form of the byte array
444     */

445     protected static String JavaDoc toHexString(byte[] data, int offset, int length)
446     {
447         StringBuffer JavaDoc s = new StringBuffer JavaDoc(length*2);
448         int end = offset+length;
449
450         for (int i = offset; i < end; i++)
451         {
452             int high_nibble = (data[i] & 0xf0) >>> 4;
453             int low_nibble = (data[i] & 0x0f);
454             s.append(hex_table[high_nibble]);
455             s.append(hex_table[low_nibble]);
456         }
457
458         return s.toString();
459     }
460
461     /**
462   
463         Convert a string into a byte array in hex format.
464         <BR>
465         For each character (b) two bytes are generated, the first byte
466         represents the high nibble (4 bits) in hexidecimal (<code>b & 0xf0</code>),
467         the second byte
468         represents the low nibble (<code>b & 0x0f</code>).
469         <BR>
470         The character at <code>str.charAt(0)</code> is represented by the first two bytes
471         in the returned String.
472
473         @param str string
474         @param offset starting character (zero based) to convert.
475         @param length number of characters to convert.
476
477         @return the byte[] (with hexidecimal format) form of the string (str)
478     */

479     protected static byte[] toHexByte(String JavaDoc str, int offset, int length)
480     {
481         byte[] data = new byte[(length - offset) * 2];
482         int end = offset+length;
483
484         for (int i = offset; i < end; i++)
485         {
486             char ch = str.charAt(i);
487             int high_nibble = (ch & 0xf0) >>> 4;
488             int low_nibble = (ch & 0x0f);
489             data[i] = (byte)high_nibble;
490             data[i+1] = (byte)low_nibble;
491         }
492         return data;
493     }
494 }
495
Popular Tags