KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > derby > client > am > EncryptionManager


1 /*
2
3    Derby - Class org.apache.derby.client.am.EncryptionManager
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.client.am;
23
24 import java.security.Provider JavaDoc;
25 import java.security.Security JavaDoc;
26 import org.apache.derby.shared.common.reference.SQLState;
27 import org.apache.derby.shared.common.sanity.SanityManager;
28
29 // This class is get used when using encrypted password and/or userid mechanism.
30
// The <b>EncryptionManager</b> classs uses Diffie_Hellman algorithm to get the publick key and
31
// secret key, and then DES encryption is done using certain token (based on security
32
// mechanism) and server side's public key. Basically, this class is called when using
33
// security mechanism User ID and encrypted password (usrencpwd) and Encrypted user ID and password
34
// (eusridpwd).
35
// This class uses JCE provider to do Diffie_Hellman algorithm and DES encryption,
36
// obtainPublicKey(), calculateEncryptionToken(int, byte[]) and encryptData(byte[], int, byte[], byte[])
37
// The agreed public value for the Diffie-Hellman prime is 256 bits
38
// and hence the encrytion will work only if the jce provider supports a 256 bits prime
39
//
40
// This class also have methods for the SECMEC_USRSSBPWD security mechanism.
41

42 public class EncryptionManager {
43     transient Agent agent_; // for obtaining an exception log writer only
44

45     // PROTOCOL's Diffie-Hellman agreed public value: prime.
46
private static final byte modulusBytes__[] = {
47         (byte) 0xC6, (byte) 0x21, (byte) 0x12, (byte) 0xD7,
48         (byte) 0x3E, (byte) 0xE6, (byte) 0x13, (byte) 0xF0,
49         (byte) 0x94, (byte) 0x7A, (byte) 0xB3, (byte) 0x1F,
50         (byte) 0x0F, (byte) 0x68, (byte) 0x46, (byte) 0xA1,
51         (byte) 0xBF, (byte) 0xF5, (byte) 0xB3, (byte) 0xA4,
52         (byte) 0xCA, (byte) 0x0D, (byte) 0x60, (byte) 0xBC,
53         (byte) 0x1E, (byte) 0x4C, (byte) 0x7A, (byte) 0x0D,
54         (byte) 0x8C, (byte) 0x16, (byte) 0xB3, (byte) 0xE3
55     };
56
57     //the prime value in BigInteger form. It has to be in BigInteger form because this
58
//is the form used in JCE library.
59
private static final java.math.BigInteger JavaDoc modulus__
60             = new java.math.BigInteger JavaDoc(1, modulusBytes__);
61
62     // PROTOCOL's Diffie-Hellman agreed public value: base.
63
private static final byte baseBytes__[] = {
64         (byte) 0x46, (byte) 0x90, (byte) 0xFA, (byte) 0x1F,
65         (byte) 0x7B, (byte) 0x9E, (byte) 0x1D, (byte) 0x44,
66         (byte) 0x42, (byte) 0xC8, (byte) 0x6C, (byte) 0x91,
67         (byte) 0x14, (byte) 0x60, (byte) 0x3F, (byte) 0xDE,
68         (byte) 0xCF, (byte) 0x07, (byte) 0x1E, (byte) 0xDC,
69         (byte) 0xEC, (byte) 0x5F, (byte) 0x62, (byte) 0x6E,
70         (byte) 0x21, (byte) 0xE2, (byte) 0x56, (byte) 0xAE,
71         (byte) 0xD9, (byte) 0xEA, (byte) 0x34, (byte) 0xE4
72     };
73
74     // The base value in BigInteger form.
75
private static final java.math.BigInteger JavaDoc base__ =
76             new java.math.BigInteger JavaDoc(1, baseBytes__);
77
78     //PROTOCOL's Diffie-Hellman agreed exponential length
79
private static final int exponential_length__ = 255;
80
81     private javax.crypto.spec.DHParameterSpec paramSpec_;
82     private java.security.KeyPairGenerator JavaDoc keyPairGenerator_;
83     private java.security.KeyPair JavaDoc keyPair_;
84     private javax.crypto.KeyAgreement keyAgreement_;
85
86     private byte[] token_; // init vector
87
private byte[] secKey_; // security key
88
private javax.crypto.SecretKeyFactory secretKeyFactory_ = null;
89     private String JavaDoc providerName; // security provider name
90
private Provider JavaDoc provider;
91
92     // Required for SECMEC_USRSSBPWD DRDA security mechanism
93
// NOTE: In a next incarnation, these constants are being moved
94
// to a dedicated/specialized SecMec_USRSSBPWD class implementing
95
// a SecurityMechanism interface.
96
private java.security.MessageDigest JavaDoc messageDigest = null;
97     private java.security.SecureRandom JavaDoc secureRandom = null;
98     private final static int SECMEC_USRSSBPWD_SEED_LEN = 8; // Seed length
99
// PWSEQs's 8-byte value constant - See DRDA Vol 3
100
private static final byte SECMEC_USRSSBPWD_PWDSEQS[] = {
101         (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
102         (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01
103     };
104     // Random Number Generator (PRNG) Algorithm
105
private final static String JavaDoc SHA_1_PRNG_ALGORITHM = "SHA1PRNG";
106     public final static String JavaDoc SHA_1_DIGEST_ALGORITHM = "SHA-1";
107
108     // EncryptionManager constructor. In this constructor,DHParameterSpec,
109
// KeyPairGenerator, KeyPair, and KeyAgreement are initialized.
110
public EncryptionManager(Agent agent) throws SqlException {
111         agent_ = agent;
112         try {
113             // get a security provider that supports the diffie helman key agreement algorithm
114
Provider JavaDoc[] list = Security.getProviders("KeyAgreement.DH");
115             if (list == null) {
116                 throw new java.security.NoSuchProviderException JavaDoc();
117             }
118             provider = list[0];
119             providerName = provider.getName();
120             paramSpec_ = new javax.crypto.spec.DHParameterSpec(modulus__, base__, exponential_length__);
121             keyPairGenerator_ = java.security.KeyPairGenerator.getInstance("DH", providerName);
122             keyPairGenerator_.initialize(paramSpec_);
123             keyPair_ = keyPairGenerator_.generateKeyPair();
124             keyAgreement_ = javax.crypto.KeyAgreement.getInstance("DH", providerName);
125             keyAgreement_.init(keyPair_.getPrivate());
126         } catch (java.security.GeneralSecurityException JavaDoc e) {
127             throw new SqlException(agent_.logWriter_,
128                 new ClientMessageId(SQLState.SECURITY_EXCEPTION_ENCOUNTERED), e);
129         }
130     }
131
132     // Retrieve a particular instance of the Encryption manager for a given
133
// (Messsage Digest) algorithm. This is currently required for the
134
// SECMEC_USRSSBPWD (strong password substitute) security mechanism.
135
//
136
// NOTE: This is temporary logic as the encryption manager is being
137
// rewritten into a DRDASecurityManager and have some of the
138
// client/engine common logic moved to the Derby 'shared' package.
139
public EncryptionManager(Agent agent, String JavaDoc algorithm) throws SqlException {
140         agent_ = agent;
141         try {
142             // Instantiate the encryption manager for the passed-in security
143
// algorithm and this from the default provider
144
// NOTE: We're only dealing with Message Digest algorithms for now.
145
messageDigest = java.security.MessageDigest.getInstance(algorithm);
146             // We're also verifying that we can instantiate a randon number
147
// generator (PRNG).
148
secureRandom =
149                 java.security.SecureRandom.getInstance(SHA_1_PRNG_ALGORITHM);
150         } catch (java.security.NoSuchAlgorithmException JavaDoc nsae) {
151             // The following exception should not be raised for SHA-1 type of
152
// message digest as we've already verified during boot-up that this
153
// algorithm was available as part of the JRE (since BUILT-IN
154
// authentication requires it); but we still raise the exception if
155
// a client were to request a different algorithm.
156
throw new SqlException(agent_.logWriter_,
157                 new ClientMessageId(SQLState.SECURITY_EXCEPTION_ENCOUNTERED),
158                                     nsae);
159         }
160     }
161
162     // This method generates the public key and returns it. This
163
// shared public key is the application requester's connection key and will
164
// be exchanged with the application server's connection key. This connection
165
// key will be put in the sectkn in ACCSEC command and send to the application
166
// server.
167
// @param null
168
// @return a byte array that is the application requester's public key
169
public byte[] obtainPublicKey() {
170
171         //we need to get the plain form public key because PROTOCOL accepts plain form
172
//public key only.
173
java.math.BigInteger JavaDoc aPub = ((javax.crypto.interfaces.DHPublicKey) keyPair_.getPublic()).getY();
174         byte[] aPubKey = aPub.toByteArray();
175
176         //the following lines of code is to adjust the length of the key. PublicKey
177
//in JCE is in the form of BigInteger and it's a signed value. When tranformed
178
//to a Byte array form, normally this array is 32 bytes. However, if the
179
//value happens to take up all 32 X 8 bits and it is positive, an extra
180
//bit is needed and then a 33 byte array will be returned. Since PROTOCOL can't
181
//recogize the 33 byte key, we check the length here, if the length is 33,
182
//we will just trim off the first byte (0) and get the rest of 32 bytes.
183
if (aPubKey.length == 33 && aPubKey[0] == 0) {
184             //System.out.println ("Adjust length");
185
byte[] newKey = new byte[32];
186             for (int i = 0; i < newKey.length; i++) {
187                 newKey[i] = aPubKey[i + 1];
188             }
189             return newKey;
190         }
191
192         //the following lines of code is to adjust the length of the key. Occasionally,
193
//the length of the public key is less than 32, the reason of this is that the 0 byte
194
//in the beginning is somehow not returned. So we check the length here, if the length
195
//is less than 32, we will pad 0 in the beginning to make the public key 32 bytes
196
if (aPubKey.length < 32) {
197             byte[] newKey = new byte[32];
198             int i;
199             for (i = 0; i < 32 - aPubKey.length; i++) {
200                 newKey[i] = 0;
201             }
202             for (int j = i; j < newKey.length; j++) {
203                 newKey[j] = aPubKey[j - i];
204             }
205             return newKey;
206         }
207         return aPubKey;
208     }
209
210     // This method is used to calculate the encryption token. DES encrypts the
211
// data using a token and the generated shared private key. The token used
212
// depends on the type of security mechanism being used:
213
// USRENCPWD - The userid is used as the token. The USRID is zero-padded to
214
// 8 bytes if less than 8 bytes or truncated to 8 bytes if greater than 8 bytes.
215
// EUSRIDPWD - The middle 8 bytes of the server's connection key is used as
216
// the token.
217
// @param int securityMechanism
218
// @param byte[] userid or server's connection key
219
// @return byte[] the encryption token
220
private byte[] calculateEncryptionToken(int securityMechanism, byte[] initVector) {
221         byte[] token = new byte[8];
222
223         //USRENCPWD, the userid is used as token
224
if (securityMechanism == 7) {
225             if (initVector.length < 8) { //shorter than 8 bytes, zero padded to 8 bytes
226
for (int i = 0; i < initVector.length; i++) {
227                     token[i] = initVector[i];
228                 }
229                 for (int i = initVector.length; i < 8; i++) {
230                     token[i] = 0;
231                 }
232             } else { //longer than 8 bytes, truncated to 8 bytes
233
for (int i = 0; i < 8; i++) {
234                     token[i] = initVector[i];
235                 }
236             }
237         }
238         //EUSRIDPWD - The middle 8 bytes of the server's connection key is used as
239
//the token.
240
else if (securityMechanism == 9) {
241             for (int i = 0; i < 8; i++) {
242                 token[i] = initVector[i + 12];
243             }
244         }
245         return token;
246     }
247
248     //JDK 1.4 has a parity check on the DES encryption key. Each byte needs to have an odd number
249
//of "1"s in it, and this is required by DES. Otherwise JDK 1.4 throws InvalidKeyException.
250
//Older JDK doesn't check this. In order to make encryption work with JDK1.4, we are going to
251
//check each of the 8 byte of our key and flip the last bit if it has even number of 1s.
252
private void keyParityCheck(byte[] key) throws SqlException {
253         byte temp;
254         int changeParity;
255         if (key.length != 8) {
256             throw new SqlException(agent_.logWriter_,
257                 new ClientMessageId(SQLState.DES_KEY_HAS_WRONG_LENGTH),
258                 new Integer JavaDoc(8), new Integer JavaDoc(key.length));
259                         
260         }
261         for (int i = 0; i < 8; i++) {
262             temp = key[i];
263             changeParity = 1;
264             for (int j = 0; j < 8; j++) {
265                 if (temp < 0) {
266                     changeParity = 1 - changeParity;
267                 }
268                 temp = (byte) (temp << 1);
269             }
270             if (changeParity == 1) {
271                 if ((key[i] & 1) != 0) {
272                     key[i] &= 0xfe;
273                 } else {
274                     key[i] |= 1;
275                 }
276             }
277         }
278     }
279
280     // This method generates a secret key using the application server's
281
// public key
282
private byte[] generatePrivateKey(byte[] targetPublicKey) throws SqlException {
283         try {
284
285             //initiate a Diffie_Hellman KeyFactory object.
286
java.security.KeyFactory JavaDoc keyFac = java.security.KeyFactory.getInstance("DH", provider);
287
288             //Use server's public key to initiate a DHPublicKeySpec and then use
289
//this DHPublicKeySpec to initiate a publicKey object
290
java.math.BigInteger JavaDoc publicKey = new java.math.BigInteger JavaDoc(1, targetPublicKey);
291             javax.crypto.spec.DHPublicKeySpec dhKeySpec =
292                     new javax.crypto.spec.DHPublicKeySpec(publicKey, modulus__, base__);
293             java.security.PublicKey JavaDoc pubKey = keyFac.generatePublic(dhKeySpec);
294
295             //Execute the first phase of DH keyagreement protocal.
296
keyAgreement_.doPhase(pubKey, true);
297
298             //generate the shared secret key. The application requestor's shared secret
299
//key should be exactly the same as the application server's shared secret
300
//key
301
byte[] sharedSecret = keyAgreement_.generateSecret();
302             byte[] newKey = new byte[32];
303
304             //We adjust the length here. If the length of secret key is 33 and the first byte is 0,
305
//we trim off the frist byte. If the length of secret key is less than 32, we will
306
//pad 0 to the beginning of the byte array tho make the secret key 32 bytes.
307
if (sharedSecret.length == 33 && sharedSecret[0] == 0) {
308                 for (int i = 0; i < newKey.length; i++) {
309                     newKey[i] = sharedSecret[i + 1];
310                 }
311
312             }
313             if (sharedSecret.length < 32) {
314                 int i;
315                 for (i = 0; i < (32 - sharedSecret.length); i++) {
316                     newKey[i] = 0;
317                 }
318                 for (int j = i; j < sharedSecret.length; j++) {
319                     newKey[j] = sharedSecret[j - i];
320                 }
321             }
322
323             //The Data Encryption Standard (DES) is going to be used to encrypt userid
324
//and password. DES is a block cipher; it encrypts data in 64-bit blocks.
325
//PROTOCOL encryption uses DES CBC mode as defined by the FIPS standard
326
//DES CBC requires an encryption key and an 8 byte token to encrypt the data.
327
//The middle 8 bytes of Diffie-Hellman shared private key is used as the
328
//encryption key. The following code retrieves middle 8 bytes of the shared
329
//private key.
330
byte[] key = new byte[8];
331
332             //if secret key is not 32, we will use the adjust length secret key
333
if (sharedSecret.length == 32) {
334                 for (int i = 0; i < 8; i++) {
335                     key[i] = sharedSecret[i + 12];
336                 }
337             } else if (sharedSecret.length == 33 || sharedSecret.length < 32) {
338                 for (int i = 0; i < 8; i++) {
339                     key[i] = newKey[i + 12];
340                 }
341             } else {
342                 throw new SqlException(agent_.logWriter_,
343                     new ClientMessageId(SQLState.SHARED_KEY_LENGTH_ERROR),
344                     new Integer JavaDoc(sharedSecret.length));
345             }
346
347             //we do parity check here and flip the parity bit if the byte has even number of 1s
348
keyParityCheck(key);
349             return key;
350         }
351         catch (java.security.GeneralSecurityException JavaDoc e) {
352             throw new SqlException(agent_.logWriter_,
353                 new ClientMessageId(SQLState.SECURITY_EXCEPTION_ENCOUNTERED), e);
354         }
355     }
356
357     // This method encrypts the usreid/password with the middle 8 bytes of
358
// the generated secret key and an encryption token. Then it returns the
359
// encrypted data in a byte array.
360
// plainText The byte array form userid/password to encrypt.
361
// initVector The byte array which is used to calculate the
362
// encryption token.
363
// targetPublicKey DERBY' public key.
364
// Returns the encrypted data in a byte array.
365
public byte[] encryptData(byte[] plainText,
366                               int securityMechanism,
367                               byte[] initVector,
368                               byte[] targetPublicKey) throws SqlException {
369
370         byte[] cipherText = null;
371         java.security.Key JavaDoc key = null;
372
373         if (token_ == null) {
374             token_ = calculateEncryptionToken(securityMechanism, initVector);
375         }
376
377         try {
378             if (secKey_ == null) {
379                 //use this encryption key to initiate a SecretKeySpec object
380
secKey_ = generatePrivateKey(targetPublicKey);
381                 javax.crypto.spec.SecretKeySpec desKey = new javax.crypto.spec.SecretKeySpec(secKey_, "DES");
382                 key = desKey;
383             } else {
384                 //use this encryption key to initiate a SecretKeySpec object
385
javax.crypto.spec.DESKeySpec desKey = new javax.crypto.spec.DESKeySpec(secKey_);
386                 if (secretKeyFactory_ == null) {
387                     secretKeyFactory_ = javax.crypto.SecretKeyFactory.getInstance("DES", providerName);
388                 }
389                 key = secretKeyFactory_.generateSecret(desKey);
390             }
391
392             //We use DES in CBC mode because this is the mode used in PROTOCOL. The
393
//encryption mode has to be consistent for encryption and decryption.
394
//CBC mode requires an initialization vector(IV) parameter. In CBC mode
395
//we need to initialize the Cipher object with an IV, which can be supplied
396
// using the javax.crypto.spec.IvParameterSpec class.
397
javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("DES/CBC/PKCS5Padding", providerName);
398
399             //generate a IVParameterSpec object and use it to initiate the
400
//Cipher object.
401
javax.crypto.spec.IvParameterSpec ivParam = new javax.crypto.spec.IvParameterSpec(token_);
402
403             //initiate the Cipher using encryption mode, encryption key and the
404
//IV parameter.
405
cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, key, ivParam);
406
407             //Execute the final phase of encryption
408
cipherText = cipher.doFinal(plainText);
409         } catch (javax.crypto.NoSuchPaddingException e) {
410             throw new SqlException(agent_.logWriter_,
411                         new ClientMessageId(SQLState.CRYPTO_NO_SUCH_PADDING));
412         } catch (javax.crypto.BadPaddingException e) {
413             throw new SqlException(agent_.logWriter_,
414                         new ClientMessageId(SQLState.CRYPTO_BAD_PADDING));
415         } catch (javax.crypto.IllegalBlockSizeException e) {
416             throw new SqlException(agent_.logWriter_,
417                         new ClientMessageId(SQLState.CRYPTO_ILLEGAL_BLOCK_SIZE));
418         } catch (java.security.GeneralSecurityException JavaDoc e) {
419             throw new SqlException(agent_.logWriter_,
420                 new ClientMessageId(SQLState.SECURITY_EXCEPTION_ENCOUNTERED), e);
421         }
422
423         return cipherText;
424     }
425
426
427     // This method decrypts the usreid/password with the middle 8 bytes of
428
// the generated secret key and an encryption token. Then it returns the
429
// decrypted data in a byte array.
430
// plainText The byte array form userid/password to encrypt.
431
// initVector The byte array which is used to calculate the
432
// encryption token.
433
// targetPublicKey DERBY' public key.
434
// Returns the decrypted data in a byte array.
435
public byte[] decryptData(byte[] cipherText,
436                               int securityMechanism,
437                               byte[] initVector,
438                               byte[] targetPublicKey) throws SqlException {
439
440         byte[] plainText = null;
441         java.security.Key JavaDoc key = null;
442
443         if (token_ == null) {
444             token_ = calculateEncryptionToken(securityMechanism, initVector);
445         }
446
447         try {
448             if (secKey_ == null) {
449                 //use this encryption key to initiate a SecretKeySpec object
450
secKey_ = generatePrivateKey(targetPublicKey);
451                 javax.crypto.spec.SecretKeySpec desKey = new javax.crypto.spec.SecretKeySpec(secKey_, "DES");
452                 key = desKey;
453             } else {
454                 //use this encryption key to initiate a SecretKeySpec object
455
javax.crypto.spec.DESKeySpec desKey = new javax.crypto.spec.DESKeySpec(secKey_);
456                 if (secretKeyFactory_ == null) {
457                     secretKeyFactory_ = javax.crypto.SecretKeyFactory.getInstance("DES", providerName);
458                 }
459                 key = secretKeyFactory_.generateSecret(desKey);
460             }
461
462             //We use DES in CBC mode because this is the mode used in PROTOCOL. The
463
//encryption mode has to be consistent for encryption and decryption.
464
//CBC mode requires an initialization vector(IV) parameter. In CBC mode
465
//we need to initialize the Cipher object with an IV, which can be supplied
466
// using the javax.crypto.spec.IvParameterSpec class.
467
javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("DES/CBC/PKCS5Padding", providerName);
468
469             //generate a IVParameterSpec object and use it to initiate the
470
//Cipher object.
471
javax.crypto.spec.IvParameterSpec ivParam = new javax.crypto.spec.IvParameterSpec(token_);
472
473             //initiate the Cipher using encryption mode, encryption key and the
474
//IV parameter.
475
cipher.init(javax.crypto.Cipher.DECRYPT_MODE, key, ivParam);
476
477             //Execute the final phase of encryption
478
plainText = cipher.doFinal(cipherText);
479         } catch (javax.crypto.NoSuchPaddingException e) {
480             throw new SqlException(agent_.logWriter_,
481                         new ClientMessageId(SQLState.CRYPTO_NO_SUCH_PADDING));
482         } catch (javax.crypto.BadPaddingException e) {
483             throw new SqlException(agent_.logWriter_,
484                         new ClientMessageId(SQLState.CRYPTO_BAD_PADDING));
485         } catch (javax.crypto.IllegalBlockSizeException e) {
486             throw new SqlException(agent_.logWriter_,
487                         new ClientMessageId(SQLState.CRYPTO_ILLEGAL_BLOCK_SIZE));
488         } catch (java.security.GeneralSecurityException JavaDoc e) {
489             throw new SqlException(agent_.logWriter_,
490                 new ClientMessageId(SQLState.SECURITY_EXCEPTION_ENCOUNTERED), e);
491         }
492         return plainText;
493     }
494
495     public void setInitVector(byte[] initVector) {
496         token_ = initVector;
497     }
498
499     public void setSecKey(byte[] secKey) {
500         secKey_ = secKey;
501     }
502
503     public void resetSecurityKeys() {
504         token_ = null;
505         secKey_ = null;
506     }
507
508     /****************************************************************
509      * Below are methods for the SECMEC_USRSSBPWD security mechanism.
510      ****************************************************************/

511
512     /**
513      * This method generates an 8-Byte random seed for the client (source).
514      *
515      * @return a random 8-Byte seed.
516      */

517     public byte[] generateSeed() {
518         byte randomSeedBytes[] = new byte[SECMEC_USRSSBPWD_SEED_LEN];
519         secureRandom.setSeed(secureRandom.generateSeed(
520                                         SECMEC_USRSSBPWD_SEED_LEN));
521         secureRandom.nextBytes(randomSeedBytes);
522         return randomSeedBytes;
523     }
524
525     /**
526      * Strong Password Substitution (USRSSBPWD).
527      *
528      * This method generate a password subtitute to send to the target
529      * server.
530      *
531      * Substitution algorithm works as follow:
532      *
533      * PW_TOKEN = SHA-1(PW, ID)
534      * The password (PW) and user name (ID) can be of any length greater
535      * than or equal to 1 byte.
536      * The client generates a 20-byte password substitute (PW_SUB) as follows:
537      * PW_SUB = SHA-1(PW_TOKEN, RDr, RDs, ID, PWSEQs)
538      *
539      * w/ (RDs) as the random client seed and (RDr) as the server one.
540      *
541      * See PWDSSB - Strong Password Substitution Security Mechanism
542      * (DRDA Vol.3 - P.650)
543      *
544      * @param userName The user's name
545      * @param password The user's password
546      * @param sourceSeed_ random client seed (RDs)
547      * @param targetSeed_ random server seed (RDr)
548      *
549      * @return a password substitute.
550      */

551     public byte[] substitutePassword(
552                 String JavaDoc userName,
553                 String JavaDoc password,
554                 byte[] sourceSeed_,
555                 byte[] targetSeed_) throws SqlException {
556
557         // Pattern that is prefixed to the BUILTIN encrypted password
558
String JavaDoc ID_PATTERN_NEW_SCHEME = "3b60";
559         
560         // Generated password substitute
561
byte[] passwordSubstitute;
562
563         // Assert we have a SHA-1 Message Digest already instantiated
564
if (SanityManager.DEBUG) {
565             SanityManager.ASSERT((messageDigest != null) &&
566                                  (SHA_1_DIGEST_ALGORITHM.equals(
567                                     messageDigest.getAlgorithm())));
568         }
569
570         // IMPORTANT NOTE: As the password is stored single-hashed in the
571
// database on the target side, it is impossible for the target to
572
// decrypt the password and recompute a substitute to compare with
573
// one generated on the source side - Hence, for now we have to
574
// single-hash and encrypt the password the same way the target is
575
// doing it and we will still generate a substitute obviously - The
576
// password, even pre-hashed will never make it across the wire as
577
// a substitute is generated. In other words, if the target cannot
578
// figure what the original password is (because of not being able
579
// to decrypt it or not being able to retrieve it (i.e. LDAP), then
580
// It may be problematic - so in a way, Strong Password Substitution
581
// (USRSSBPWD) cannot be supported for targets which can't access or
582
// decrypt some password on their side.
583
//
584
// So in short, SECMEC_USRSSBPWD is only supported if the
585
// authentication provider on the target side is NONE or Derby's
586
// BUILTIN one and if using Derby's Client Network driver (for now).
587
//
588
// Encrypt the password as it is done by the derby engine - Note that
589
// this code (logic) is not shared yet - will be in next revision.
590
messageDigest.reset();
591
592         messageDigest.update(this.toHexByte(password, 0, password.length()));
593         byte[] encryptVal = messageDigest.digest();
594         String JavaDoc hexString = ID_PATTERN_NEW_SCHEME +
595                      this.toHexString(encryptVal, 0, encryptVal.length);
596
597         // Generate some 20-byte password token
598
byte[] userBytes = this.toHexByte(userName, 0, userName.length());
599         messageDigest.update(userBytes);
600         messageDigest.update(this.toHexByte(hexString, 0, hexString.length()));
601         byte[] passwordToken = messageDigest.digest();
602         
603         // Now we generate the 20-byte password substitute
604
messageDigest.update(passwordToken);
605         messageDigest.update(targetSeed_);
606         messageDigest.update(sourceSeed_);
607         messageDigest.update(userBytes);
608         messageDigest.update(SECMEC_USRSSBPWD_PWDSEQS);
609
610         passwordSubstitute = messageDigest.digest();
611
612         return passwordSubstitute;
613     }
614
615     /*********************************************************************
616      * RESOLVE: *
617      * The methods and static vars below should go into some 'shared' *
618      * package when the capability is put back in (StringUtil.java). *
619      *********************************************************************/

620
621     private static char[] hex_table = {
622                 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
623                 'a', 'b', 'c', 'd', 'e', 'f'
624             };
625
626     /**
627         Convert a byte array to a String with a hexidecimal format.
628         The String may be converted back to a byte array using fromHexString.
629         <BR>
630         For each byte (b) two characaters are generated, the first character
631         represents the high nibble (4 bits) in hexidecimal (<code>b & 0xf0</code>),
632         the second character represents the low nibble (<code>b & 0x0f</code>).
633         <BR>
634         The byte at <code>data[offset]</code> is represented by the first two
635         characters in the returned String.
636
637         @param data byte array
638         @param offset starting byte (zero based) to convert.
639         @param length number of bytes to convert.
640
641         @return the String (with hexidecimal format) form of the byte array
642     */

643     private String JavaDoc toHexString(byte[] data, int offset, int length)
644     {
645         StringBuffer JavaDoc s = new StringBuffer JavaDoc(length*2);
646         int end = offset+length;
647
648         for (int i = offset; i < end; i++)
649         {
650             int high_nibble = (data[i] & 0xf0) >>> 4;
651             int low_nibble = (data[i] & 0x0f);
652             s.append(hex_table[high_nibble]);
653             s.append(hex_table[low_nibble]);
654         }
655
656         return s.toString();
657     }
658
659     /**
660   
661         Convert a string into a byte array in hex format.
662         <BR>
663         For each character (b) two bytes are generated, the first byte
664         represents the high nibble (4 bits) in hexidecimal (<code>b & 0xf0</code>),
665         the second byte represents the low nibble (<code>b & 0x0f</code>).
666         <BR>
667         The character at <code>str.charAt(0)</code> is represented by the first two bytes
668         in the returned String.
669
670         @param str string
671         @param offset starting character (zero based) to convert.
672         @param length number of characters to convert.
673
674         @return the byte[] (with hexidecimal format) form of the string (str)
675     */

676     private byte[] toHexByte(String JavaDoc str, int offset, int length)
677     {
678         byte[] data = new byte[(length - offset) * 2];
679         int end = offset+length;
680
681         for (int i = offset; i < end; i++)
682         {
683             char ch = str.charAt(i);
684             int high_nibble = (ch & 0xf0) >>> 4;
685             int low_nibble = (ch & 0x0f);
686             data[i] = (byte)high_nibble;
687             data[i+1] = (byte)low_nibble;
688         }
689         return data;
690     }
691 }
692
Popular Tags