001: /*
002:
003: Derby - Class org.apache.derby.impl.drda.DecryptionManager
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to You under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.impl.drda;
023:
024: import java.security.KeyPairGenerator;
025: import java.security.KeyPair;
026: import javax.crypto.KeyAgreement;
027: import javax.crypto.spec.DHParameterSpec;
028: import javax.crypto.interfaces.DHPublicKey;
029: import javax.crypto.spec.DHPublicKeySpec;
030: import javax.crypto.spec.SecretKeySpec;
031: import javax.crypto.Cipher;
032: import javax.crypto.spec.IvParameterSpec;
033: import java.security.spec.AlgorithmParameterSpec;
034: import java.security.KeyFactory;
035: import java.security.PublicKey;
036: import java.sql.SQLException;
037: import java.math.BigInteger;
038: import org.apache.derby.shared.common.sanity.SanityManager;
039:
040: /**
041: * This class is used to decrypt password and/or userid.
042: * It uses Diffie_Hellman algorithm to get the publick key and secret key, and then
043: * DES encryption is done using certain token (based on security mechanism) and
044: * this side's own public key. Basically, this class is called when using a security
045: * mechanism that encrypts user ID and password (eusridpwd). This class uses IBM JCE
046: * to do Diffie_Hellman algorithm and DES encryption.
047: */
048:
049: class DecryptionManager {
050: // DRDA's Diffie-Hellman agreed public value: prime.
051: private static final byte modulusBytes__[] = { (byte) 0xC6,
052: (byte) 0x21, (byte) 0x12, (byte) 0xD7, (byte) 0x3E,
053: (byte) 0xE6, (byte) 0x13, (byte) 0xF0, (byte) 0x94,
054: (byte) 0x7A, (byte) 0xB3, (byte) 0x1F, (byte) 0x0F,
055: (byte) 0x68, (byte) 0x46, (byte) 0xA1, (byte) 0xBF,
056: (byte) 0xF5, (byte) 0xB3, (byte) 0xA4, (byte) 0xCA,
057: (byte) 0x0D, (byte) 0x60, (byte) 0xBC, (byte) 0x1E,
058: (byte) 0x4C, (byte) 0x7A, (byte) 0x0D, (byte) 0x8C,
059: (byte) 0x16, (byte) 0xB3, (byte) 0xE3 };
060:
061: //the prime value in BigInteger form. It has to be in BigInteger form because this
062: //is the form used in JCE library.
063: private static final BigInteger modulus__ = new BigInteger(1,
064: modulusBytes__);
065:
066: // DRDA's Diffie-Hellman agreed public value: base.
067: private static final byte baseBytes__[] = { (byte) 0x46,
068: (byte) 0x90, (byte) 0xFA, (byte) 0x1F, (byte) 0x7B,
069: (byte) 0x9E, (byte) 0x1D, (byte) 0x44, (byte) 0x42,
070: (byte) 0xC8, (byte) 0x6C, (byte) 0x91, (byte) 0x14,
071: (byte) 0x60, (byte) 0x3F, (byte) 0xDE, (byte) 0xCF,
072: (byte) 0x07, (byte) 0x1E, (byte) 0xDC, (byte) 0xEC,
073: (byte) 0x5F, (byte) 0x62, (byte) 0x6E, (byte) 0x21,
074: (byte) 0xE2, (byte) 0x56, (byte) 0xAE, (byte) 0xD9,
075: (byte) 0xEA, (byte) 0x34, (byte) 0xE4 };
076:
077: // The base value in BigInteger form. It has to be in BigInteger form because
078: //this is the form used in IBM JCE library.
079: private static final BigInteger base__ = new BigInteger(1,
080: baseBytes__);
081:
082: //DRDA's Diffie-Hellman agreed exponential length
083: private static final int exponential_length__ = 255;
084:
085: private KeyPairGenerator keyPairGenerator_;
086: private KeyPair keyPair_;
087: private KeyAgreement keyAgreement_;
088: private DHParameterSpec paramSpec_;
089:
090: // Random Number Generator (PRNG) Algorithm
091: private final static String SHA_1_PRNG_ALGORITHM = "SHA1PRNG";
092: private final static int SECMEC_USRSSBPWD_SEED_LEN = 8; // Seed length
093:
094: /**
095: * EncryptionManager constructor. In this constructor,DHParameterSpec,
096: * KeyPairGenerator, KeyPair, and KeyAgreement are initialized.
097: *
098: * @throws SQLException that wraps any error
099: */
100: DecryptionManager() throws SQLException {
101: try {
102: if (java.security.Security.getProvider("IBMJCE") == null) // IBMJCE is not installed, install it.
103: java.security.Security
104: .addProvider((java.security.Provider) Class
105: .forName("IBMJCE").newInstance());
106: paramSpec_ = new DHParameterSpec(modulus__, base__,
107: exponential_length__);
108: keyPairGenerator_ = KeyPairGenerator.getInstance("DH",
109: "IBMJCE");
110: keyPairGenerator_
111: .initialize((AlgorithmParameterSpec) paramSpec_);
112: keyPair_ = keyPairGenerator_.generateKeyPair();
113: keyAgreement_ = KeyAgreement.getInstance("DH", "IBMJCE");
114: keyAgreement_.init(keyPair_.getPrivate());
115: } catch (java.lang.ClassNotFoundException e) {
116: throw new SQLException(
117: "java.lang.ClassNotFoundException is caught"
118: + " when initializing EncryptionManager '"
119: + e.getMessage() + "'");
120: } catch (java.lang.IllegalAccessException e) {
121: throw new SQLException(
122: "java.lang.IllegalAccessException is caught"
123: + " when initializing EncryptionManager '"
124: + e.getMessage() + "'");
125: } catch (java.lang.InstantiationException e) {
126: throw new SQLException(
127: "java.lang.InstantiationException is caught"
128: + " when initializing EncryptionManager '"
129: + e.getMessage() + "'");
130: } catch (java.security.NoSuchProviderException e) {
131: throw new SQLException(
132: "java.security.NoSuchProviderException is caught"
133: + " when initializing EncryptionManager '"
134: + e.getMessage() + "'");
135: } catch (java.security.NoSuchAlgorithmException e) {
136: throw new SQLException(
137: "java.security.NoSuchAlgorithmException is caught"
138: + " when initializing EncryptionManager '"
139: + e.getMessage() + "'");
140: } catch (java.security.InvalidAlgorithmParameterException e) {
141: throw new SQLException(
142: "java.security.InvalidAlgorithmParameterException is caught"
143: + " when initializing EncryptionManager '"
144: + e.getMessage() + "'");
145: }
146:
147: catch (java.security.InvalidKeyException e) {
148: throw new SQLException(
149: "java.security.InvalidKeyException is caught"
150: + " when initializing EncryptionManager '"
151: + e.getMessage() + "'");
152: }
153: }
154:
155: /**
156: * This method generates the public key and returns it. This
157: * shared public key is the application server's connection key and will
158: * be exchanged with the application requester's connection key. This connection
159: * key will be put in the sectkn in ACCSECRD command and send to the application
160: * requester.
161: *
162: * @return a byte array that is the application server's public key
163: */
164: public byte[] obtainPublicKey() {
165: //The encoded public key
166: byte[] publicEnc = keyPair_.getPublic().getEncoded();
167:
168: //we need to get the plain form public key because DRDA accepts plain form
169: //public key only.
170: BigInteger aPub = ((DHPublicKey) keyPair_.getPublic()).getY();
171: byte[] aPubKey = aPub.toByteArray();
172:
173: //the following lines of code is to adjust the length of the key. PublicKey
174: //in JCE is in the form of BigInteger and it's a signed value. When tranformed
175: //to a Byte array form, normally this array is 32 bytes. However, if the
176: //value happens to take up all 32 X 8 bits and it is positive, an extra
177: //bit is needed and then a 33 byte array will be returned. Since DRDA can't
178: //recogize the 33 byte key, we check the length here, if the length is 33,
179: //we will just trim off the first byte (0) and get the rest of 32 bytes.
180: if (aPubKey.length == 33 && aPubKey[0] == 0) {
181: byte[] newKey = new byte[32];
182: for (int i = 0; i < newKey.length; i++)
183: newKey[i] = aPubKey[i + 1];
184: return newKey;
185: }
186:
187: //the following lines of code is to adjust the length of the key. Occasionally,
188: //the length of the public key is less than 32, the reason of this is that the 0 byte
189: //in the beginning is somehow not returned. So we check the length here, if the length
190: //is less than 32, we will pad 0 in the beginning to make the public key 32 bytes
191: if (aPubKey.length < 32) {
192: byte[] newKey = new byte[32];
193: int i;
194: for (i = 0; i < 32 - aPubKey.length; i++) {
195: newKey[i] = 0;
196: }
197: for (int j = i; j < newKey.length; j++)
198: newKey[j] = aPubKey[j - i];
199: return newKey;
200: }
201: return aPubKey;
202: }
203:
204: /**
205: * This method is used to calculate the decryption token. DES encrypts the
206: * data using a token and the generated shared private key. The token used
207: * depends on the type of security mechanism being used:
208: * USRENCPWD - The userid is used as the token. The USRID is zero-padded to
209: * 8 bytes if less than 8 bytes or truncated to 8 bytes if greater than 8 bytes.
210: * EUSRIDPWD - The middle 8 bytes of the server's connection key is used as
211: * the token. Decryption needs to use exactly the same token as encryption.
212: *
213: * @param securityMechanism security mechanism
214: * @param initVector userid or server(this side)'s connection key
215: * @return byte[] the decryption token
216: */
217: private byte[] calculateDecryptionToken(int securityMechanism,
218: byte[] initVector) {
219: byte[] token = new byte[8];
220:
221: //USRENCPWD, the userid is used as token
222: if (securityMechanism == 7) {
223: if (initVector.length < 8) { //shorter than 8 bytes, zero padded to 8 bytes
224: for (int i = 0; i < initVector.length; i++)
225: token[i] = initVector[i];
226: for (int i = initVector.length; i < 8; i++)
227: token[i] = 0;
228: } else { //longer than 8 bytes, truncated to 8 bytes
229: for (int i = 0; i < 8; i++)
230: token[i] = initVector[i];
231: }
232: }
233: //EUSRIDPWD - The middle 8 bytes of the server's connection key is used as
234: //the token.
235: else if (securityMechanism == 9) {
236: for (int i = 0; i < 8; i++) {
237: token[i] = initVector[i + 12];
238: }
239: }
240: return token;
241: }
242:
243: /**
244: * This method generates a secret key using the application requester's
245: * public key, and decrypts the usreid/password with the middle 8 bytes of
246: * the generated secret key and a decryption token. Then it returns the
247: * decrypted data in a byte array.
248: *
249: * @param cipherText The byte array form userid/password to decrypt.
250: * @param securityMechanism security mechanism
251: * @param initVector The byte array which is used to calculate the
252: * decryption token for initializing the cipher
253: * @param sourcePublicKey application requester (encrypter)'s public key.
254: * @return the decrypted data (plain text) in a byte array.
255: */
256: public byte[] decryptData(byte[] cipherText, int securityMechanism,
257: byte[] initVector, byte[] sourcePublicKey)
258: throws SQLException {
259: byte[] plainText = null;
260: byte[] token = calculateDecryptionToken(securityMechanism,
261: initVector);
262: try {
263:
264: //initiate a Diffie_Hellman KeyFactory object.
265: KeyFactory keyFac = KeyFactory.getInstance("DH", "IBMJCE");
266:
267: //Use server's public key to initiate a DHPublicKeySpec and then use
268: //this DHPublicKeySpec to initiate a publicKey object
269: BigInteger publicKey = new BigInteger(1, sourcePublicKey);
270: DHPublicKeySpec dhKeySpec = new DHPublicKeySpec(publicKey,
271: modulus__, base__);
272: PublicKey pubKey = keyFac.generatePublic(dhKeySpec);
273:
274: //Execute the first phase of DH keyagreement protocal.
275: keyAgreement_.doPhase(pubKey, true);
276:
277: //generate the shared secret key. The application requestor's shared secret
278: //key should be exactly the same as the application server's shared secret
279: //key
280: byte[] sharedSecret = keyAgreement_.generateSecret();
281: byte[] newKey = new byte[32];
282:
283: //We adjust the length here. If the length of secret key is 33 and the first byte is 0,
284: //we trim off the frist byte. If the length of secret key is less than 32, we will
285: //pad 0 to the beginning of the byte array tho make the secret key 32 bytes.
286: if (sharedSecret.length == 33 && sharedSecret[0] == 0) {
287: for (int i = 0; i < newKey.length; i++)
288: newKey[i] = sharedSecret[i + 1];
289: }
290: if (sharedSecret.length < 32) {
291: int i;
292: for (i = 0; i < (32 - sharedSecret.length); i++)
293: newKey[i] = 0;
294: for (int j = i; j < sharedSecret.length; j++)
295: newKey[j] = sharedSecret[j - i];
296: }
297:
298: //The Data Encryption Standard (DES) is going to be used to encrypt userid
299: //and password. DES is a block cipher; it encrypts data in 64-bit blocks.
300: //DRDA encryption uses DES CBC mode as defined by the FIPS standard
301: //DES CBC requires an encryption key and an 8 byte token to encrypt the data.
302: //The middle 8 bytes of Diffie-Hellman shared private key is used as the
303: //encryption key. The following code retrieves middle 8 bytes of the shared
304: //private key.
305: byte[] key = new byte[8];
306:
307: //if secret key is not 32, we will use the adjust length secret key
308: if (sharedSecret.length == 32) {
309: for (int i = 0; i < 8; i++)
310: key[i] = sharedSecret[i + 12];
311: } else if (sharedSecret.length == 33
312: || sharedSecret.length < 32) {
313: for (int i = 0; i < 8; i++)
314: key[i] = newKey[i + 12];
315: } else
316: throw new SQLException("sharedSecret key length error "
317: + sharedSecret.length);
318:
319: // make parity bit right, even number of 1's
320: byte temp;
321: int changeParity;
322: for (int i = 0; i < 8; i++) {
323: temp = key[i];
324: changeParity = 1;
325: for (int j = 0; j < 8; j++) {
326: if (temp < 0)
327: changeParity = 1 - changeParity;
328: temp = (byte) (temp << 1);
329: }
330: if (changeParity == 1) {
331: if ((key[i] & 1) != 0)
332: key[i] &= 0xfe;
333: else
334: key[i] |= 1;
335: }
336: }
337:
338: //use this encryption key to initiate a SecretKeySpec object
339: SecretKeySpec desKey = new SecretKeySpec(key, "DES");
340:
341: //We use DES in CBC mode because this is the mode used in DRDA. The
342: //encryption mode has to be consistent for encryption and decryption.
343: //CBC mode requires an initialization vector(IV) parameter. In CBC mode
344: //we need to initialize the Cipher object with an IV, which can be supplied
345: // using the javax.crypto.spec.IvParameterSpec class.
346: Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding",
347: "IBMJCE");
348:
349: //generate a IVParameterSpec object and use it to initiate the
350: //Cipher object.
351: IvParameterSpec ivParam = new IvParameterSpec(token);
352:
353: //initiate the Cipher using encryption mode, encryption key and the
354: //IV parameter.
355: cipher.init(javax.crypto.Cipher.DECRYPT_MODE, desKey,
356: ivParam);
357:
358: //Execute the final phase of encryption
359: plainText = cipher.doFinal(cipherText);
360: } catch (java.security.NoSuchProviderException e) {
361: throw new SQLException(
362: "java.security.NoSuchProviderException is caught "
363: + "when encrypting data '" + e.getMessage()
364: + "'");
365: } catch (java.security.NoSuchAlgorithmException e) {
366: throw new SQLException(
367: "java.security.NoSuchAlgorithmException is caught "
368: + "when encrypting data '" + e.getMessage()
369: + "'");
370: } catch (java.security.spec.InvalidKeySpecException e) {
371: throw new SQLException(
372: "java.security.InvalidKeySpecException is caught "
373: + "when encrypting data");
374: } catch (java.security.InvalidKeyException e) {
375: throw new SQLException(
376: "java.security.InvalidKeyException is caught "
377: + "when encrypting data '" + e.getMessage()
378: + "'");
379: } catch (javax.crypto.NoSuchPaddingException e) {
380: throw new SQLException(
381: "javax.crypto.NoSuchPaddingException is caught "
382: + "when encrypting data '" + e.getMessage()
383: + "'");
384: } catch (javax.crypto.BadPaddingException e) {
385: throw new SQLException(
386: "javax.crypto.BadPaddingException is caught "
387: + "when encrypting data '" + e.getMessage()
388: + "'");
389: } catch (java.security.InvalidAlgorithmParameterException e) {
390: throw new SQLException(
391: "java.security.InvalidAlgorithmParameterException is caught "
392: + "when encrypting data '" + e.getMessage()
393: + "'");
394: } catch (javax.crypto.IllegalBlockSizeException e) {
395: throw new SQLException(
396: "javax.crypto.IllegalBlockSizeException is caught "
397: + "when encrypting data '" + e.getMessage()
398: + "'");
399: }
400: return plainText;
401: }
402:
403: /**
404: * This method generates an 8-Byte random seed.
405: *
406: * Required for the SECMEC_USRSSBPWD security mechanism
407: *
408: * @return a random 8-Byte seed.
409: */
410: protected static byte[] generateSeed() throws SQLException {
411: java.security.SecureRandom secureRandom = null;
412: try {
413: // We're verifying that we can instantiate a randon number
414: // generator (PRNG).
415: secureRandom = java.security.SecureRandom
416: .getInstance(SHA_1_PRNG_ALGORITHM);
417: } catch (java.security.NoSuchAlgorithmException nsae) {
418: throw new SQLException(
419: "java.security.NoSuchAlgorithmException is caught"
420: + " when initializing DecryptionManager '"
421: + nsae.getMessage() + "'");
422: }
423: byte randomSeedBytes[] = new byte[SECMEC_USRSSBPWD_SEED_LEN];
424: secureRandom.setSeed(secureRandom
425: .generateSeed(SECMEC_USRSSBPWD_SEED_LEN));
426: secureRandom.nextBytes(randomSeedBytes);
427: // Return the 8-byte generated random seed
428: return randomSeedBytes;
429: }
430:
431: /*********************************************************************
432: * RESOLVE: *
433: * The methods and static vars below should go into some 'shared' *
434: * package when the capability is put back in (StringUtil.java). *
435: *********************************************************************/
436:
437: private static char[] hex_table = { '0', '1', '2', '3', '4', '5',
438: '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
439:
440: /**
441: Convert a byte array to a String with a hexidecimal format.
442: The String may be converted back to a byte array using fromHexString.
443: <BR>
444: For each byte (b) two characaters are generated, the first character
445: represents the high nibble (4 bits) in hexidecimal (<code>b & 0xf0</code>),
446: the second character represents the low nibble (<code>b & 0x0f</code>).
447: <BR>
448: The byte at <code>data[offset]</code> is represented by the first two characters in the returned String.
449:
450: @param data byte array
451: @param offset starting byte (zero based) to convert.
452: @param length number of bytes to convert.
453:
454: @return the String (with hexidecimal format) form of the byte array
455: */
456: protected static String toHexString(byte[] data, int offset,
457: int length) {
458: StringBuffer s = new StringBuffer(length * 2);
459: int end = offset + length;
460:
461: for (int i = offset; i < end; i++) {
462: int high_nibble = (data[i] & 0xf0) >>> 4;
463: int low_nibble = (data[i] & 0x0f);
464: s.append(hex_table[high_nibble]);
465: s.append(hex_table[low_nibble]);
466: }
467:
468: return s.toString();
469: }
470:
471: /**
472:
473: Convert a string into a byte array in hex format.
474: <BR>
475: For each character (b) two bytes are generated, the first byte
476: represents the high nibble (4 bits) in hexidecimal (<code>b & 0xf0</code>),
477: the second byte
478: represents the low nibble (<code>b & 0x0f</code>).
479: <BR>
480: The character at <code>str.charAt(0)</code> is represented by the first two bytes
481: in the returned String.
482:
483: @param str string
484: @param offset starting character (zero based) to convert.
485: @param length number of characters to convert.
486:
487: @return the byte[] (with hexidecimal format) form of the string (str)
488: */
489: protected static byte[] toHexByte(String str, int offset, int length) {
490: byte[] data = new byte[(length - offset) * 2];
491: int end = offset + length;
492:
493: for (int i = offset; i < end; i++) {
494: char ch = str.charAt(i);
495: int high_nibble = (ch & 0xf0) >>> 4;
496: int low_nibble = (ch & 0x0f);
497: data[i] = (byte) high_nibble;
498: data[i + 1] = (byte) low_nibble;
499: }
500: return data;
501: }
502: }
|