001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 *
019 */
020 package org.apache.directory.server.kerberos.shared.crypto.encryption;
021
022
023 import java.io.UnsupportedEncodingException;
024 import java.security.GeneralSecurityException;
025 import java.security.InvalidKeyException;
026 import java.security.spec.AlgorithmParameterSpec;
027
028 import javax.crypto.Cipher;
029 import javax.crypto.SecretKey;
030 import javax.crypto.spec.DESKeySpec;
031 import javax.crypto.spec.IvParameterSpec;
032 import javax.crypto.spec.SecretKeySpec;
033
034
035 /**
036 * An implementation of the DES string-to-key function as originally described
037 * in RFC 1510, "The Kerberos Network Authentication Service (V5)," and clarified
038 * in RFC 3961, "Encryption and Checksum Specifications for Kerberos 5."
039 *
040 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
041 * @version $Rev: 502338 $, $Date: 2007-02-01 11:59:43 -0800 (Thu, 01 Feb 2007) $
042 */
043 public class DesStringToKey
044 {
045 /**
046 * Returns a DES symmetric key for the given passphrase.
047 *
048 * @param passPhrase The passphrase to derive a symmetric DES key from.
049 * @return The derived symmetric DES key.
050 */
051 public byte[] getKey( String passPhrase )
052 {
053 return generateKey( passPhrase );
054 }
055
056
057 /**
058 * Returns a DES symmetric key for the given input String components,
059 * which will be concatenated in the order described in RFC's 1510 and 3961,
060 * namely password+realm+username.
061 *
062 * @param password The password.
063 * @param realmName The name of the realm.
064 * @param userName The username.
065 * @return The derived symmetric DES key.
066 */
067 public byte[] getKey( String password, String realmName, String userName )
068 {
069 return generateKey( password + realmName + userName );
070 }
071
072
073 /**
074 * Returns a DES symmetric key for the given input String.
075 *
076 * @param passPhrase The passphrase.
077 * @return The DES key.
078 * @throws Exception
079 */
080 protected byte[] generateKey( String passPhrase )
081 {
082 byte encodedByteArray[] = characterEncodeString( passPhrase );
083
084 byte paddedByteArray[] = padString( encodedByteArray );
085
086 byte[] secretKey = fanFold( paddedByteArray );
087
088 secretKey = setParity( secretKey );
089 secretKey = getStrongKey( secretKey );
090 secretKey = calculateChecksum( paddedByteArray, secretKey );
091 secretKey = setParity( secretKey );
092 secretKey = getStrongKey( secretKey );
093
094 return secretKey;
095 }
096
097
098 /**
099 * Set odd parity on an eight-byte array.
100 *
101 * @param in The byte array to set parity on.
102 * @return The parity-adjusted byte array.
103 */
104 protected byte[] setParity( byte[] in )
105 {
106 byte[] out = new byte[8];
107
108 int bitCount = 0;
109 int index = 0;
110
111 for ( int i = 0; i < 64; i++ )
112 {
113 if ( ( i + 1 ) % 8 == 0 )
114 {
115 if ( bitCount % 2 == 0 )
116 {
117 setBit( out, i, 1 );
118 }
119
120 index++;
121 bitCount = 0;
122 }
123 else
124 {
125 int val = getBit( in, index );
126 boolean bit = val > 0;
127
128 if ( bit )
129 {
130 setBit( out, i, val );
131 bitCount++;
132 }
133
134 index++;
135 }
136 }
137
138 return out;
139 }
140
141
142 /**
143 * Gets a bit at a given position.
144 *
145 * @param data
146 * @param pos
147 * @return The value of the bit.
148 */
149 protected int getBit( byte[] data, int pos )
150 {
151 int posByte = pos / 8;
152 int posBit = pos % 8;
153
154 byte valByte = data[posByte];
155 int valInt = valByte >> ( 8 - ( posBit + 1 ) ) & 0x0001;
156 return valInt;
157 }
158
159
160 /**
161 * Sets a bit at a given position.
162 *
163 * @param data
164 * @param pos
165 * @param val
166 */
167 protected void setBit( byte[] data, int pos, int val )
168 {
169 int posByte = pos / 8;
170 int posBit = pos % 8;
171 byte oldByte = data[posByte];
172 oldByte = ( byte ) ( ( ( 0xFF7F >> posBit ) & oldByte ) & 0x00FF );
173 byte newByte = ( byte ) ( ( val << ( 8 - ( posBit + 1 ) ) ) | oldByte );
174 data[posByte] = newByte;
175 }
176
177
178 /**
179 * "The top bit of each octet (always zero if the password is plain
180 * ASCII, as was assumed when the original specification was written) is
181 * discarded, and the remaining seven bits of each octet form a
182 * bitstring. This is then fan-folded and eXclusive-ORed with itself to
183 * produce a 56-bit string. An eight-octet key is formed from this
184 * string, each octet using seven bits from the bitstring, leaving the
185 * least significant bit unassigned."
186 *
187 * @param paddedByteArray The padded byte array.
188 * @return The fan-folded intermediate DES key.
189 */
190 protected byte[] fanFold( byte[] paddedByteArray )
191 {
192 byte secretKey[] = new byte[8];
193
194 int div = paddedByteArray.length / 8;
195
196 for ( int ii = 0; ii < div; ii++ )
197 {
198 byte blockValue1[] = new byte[8];
199 System.arraycopy( paddedByteArray, ii * 8, blockValue1, 0, 8 );
200
201 if ( ii % 2 == 1 )
202 {
203 byte tempbyte1 = 0;
204 byte tempbyte2 = 0;
205 byte blockValue2[] = new byte[8];
206
207 for ( int jj = 0; jj < 8; jj++ )
208 {
209 tempbyte2 = 0;
210
211 for ( int kk = 0; kk < 4; kk++ )
212 {
213 tempbyte2 = ( byte ) ( ( 1 << ( 7 - kk ) ) & 0xff );
214 tempbyte1 |= ( blockValue1[jj] & tempbyte2 ) >>> ( 7 - 2 * kk );
215 tempbyte2 = 0;
216 }
217
218 for ( int kk = 4; kk < 8; kk++ )
219 {
220 tempbyte2 = ( byte ) ( ( 1 << ( 7 - kk ) ) & 0xff );
221 tempbyte1 |= ( blockValue1[jj] & tempbyte2 ) << ( 2 * kk - 7 );
222 tempbyte2 = 0;
223 }
224
225 blockValue2[7 - jj] = tempbyte1;
226 tempbyte1 = 0;
227 }
228
229 for ( int jj = 0; jj < 8; jj++ )
230 {
231 blockValue2[jj] = ( byte ) ( ( ( blockValue2[jj] & 0xff ) >>> 1 ) & 0xff );
232 }
233
234 System.arraycopy( blockValue2, 0, blockValue1, 0, blockValue2.length );
235 }
236
237 for ( int jj = 0; jj < 8; jj++ )
238 {
239 blockValue1[jj] = ( byte ) ( ( ( blockValue1[jj] & 0xff ) << 1 ) & 0xff );
240 }
241
242 // ... eXclusive-ORed with itself to form an 8-byte DES key
243 for ( int jj = 0; jj < 8; jj++ )
244 {
245 secretKey[jj] ^= blockValue1[jj];
246 }
247 }
248
249 return secretKey;
250 }
251
252
253 /**
254 * Calculates the checksum as described in "String or Random-Data to
255 * Key Transformation." An intermediate key is used to generate a DES CBC
256 * "checksum" on the initial passphrase+salt. The encryption key is also
257 * used as the IV. The final eight-byte block is returned as the "checksum."
258 *
259 * @param data The data to encrypt.
260 * @param keyBytes The bytes of the intermediate key.
261 * @return The final eight-byte block as the checksum.
262 */
263 protected byte[] calculateChecksum( byte[] data, byte[] keyBytes )
264 {
265 try
266 {
267 Cipher cipher = Cipher.getInstance( "DES/CBC/NoPadding" );
268 SecretKey key = new SecretKeySpec( keyBytes, "DES" );
269
270 AlgorithmParameterSpec paramSpec = new IvParameterSpec( keyBytes );
271
272 cipher.init( Cipher.ENCRYPT_MODE, key, paramSpec );
273
274 byte[] result = cipher.doFinal( data );
275
276 byte[] checksum = new byte[8];
277 System.arraycopy( result, result.length - 8, checksum, 0, 8 );
278
279 return checksum;
280 }
281 catch ( GeneralSecurityException nsae )
282 {
283 nsae.printStackTrace();
284 return null;
285 }
286 }
287
288
289 /**
290 * If the secret key is weak, correct by exclusive OR'ing
291 * with the constant 0xF0.
292 *
293 * @param secretKey The key to correct, if necessary.
294 * @return The corrected key.
295 */
296 protected byte[] getStrongKey( byte[] secretKey )
297 {
298 try
299 {
300 if ( DESKeySpec.isWeak( secretKey, 0 ) )
301 {
302 secretKey[7] ^= 0xf0;
303 }
304 }
305 catch ( InvalidKeyException ike )
306 {
307 return new byte[8];
308 }
309
310 return secretKey;
311 }
312
313
314 /**
315 * Encodes string with UTF-8 encoding.
316 *
317 * @param string The String to encode.
318 * @return The encoded String.
319 */
320 protected byte[] characterEncodeString( String string )
321 {
322 byte encodedByteArray[] = new byte[string.length()];
323
324 try
325 {
326 encodedByteArray = string.getBytes( "UTF-8" );
327 }
328 catch ( UnsupportedEncodingException ue )
329 {
330 // Shouldn't ever happen for UTF-8.
331 }
332
333 return encodedByteArray;
334 }
335
336
337 /**
338 * Add padding to make an exact multiple of 8 bytes.
339 *
340 * @param encodedString
341 * @return The padded byte array.
342 */
343 protected byte[] padString( byte encodedString[] )
344 {
345 int length;
346
347 if ( encodedString.length < 8 )
348 {
349 length = encodedString.length;
350 }
351 else
352 {
353 length = encodedString.length % 8;
354 }
355
356 if ( length == 0 )
357 {
358 return encodedString;
359 }
360
361 byte paddedByteArray[] = new byte[( 8 - length ) + encodedString.length];
362
363 for ( int ii = paddedByteArray.length - 1; ii > encodedString.length - 1; ii-- )
364 {
365 paddedByteArray[ii] = 0;
366 }
367
368 System.arraycopy( encodedString, 0, paddedByteArray, 0, encodedString.length );
369
370 return paddedByteArray;
371 }
372 }