View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *  
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *  
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License. 
18   *  
19   */
20  package org.apache.directory.server.kerberos.shared.crypto.encryption;
21  
22  
23  import java.io.UnsupportedEncodingException;
24  import java.security.GeneralSecurityException;
25  import java.security.InvalidKeyException;
26  import java.security.spec.AlgorithmParameterSpec;
27  
28  import javax.crypto.Cipher;
29  import javax.crypto.SecretKey;
30  import javax.crypto.spec.DESKeySpec;
31  import javax.crypto.spec.IvParameterSpec;
32  import javax.crypto.spec.SecretKeySpec;
33  
34  
35  /**
36   * An implementation of the DES string-to-key function as originally described
37   * in RFC 1510, "The Kerberos Network Authentication Service (V5)," and clarified
38   * in RFC 3961, "Encryption and Checksum Specifications for Kerberos 5."
39   * 
40   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
41   * @version $Rev: 502338 $, $Date: 2007-02-01 11:59:43 -0800 (Thu, 01 Feb 2007) $
42   */
43  public class DesStringToKey
44  {
45      /**
46       * Returns a DES symmetric key for the given passphrase.
47       *
48       * @param passPhrase The passphrase to derive a symmetric DES key from.
49       * @return The derived symmetric DES key.
50       */
51      public byte[] getKey( String passPhrase )
52      {
53          return generateKey( passPhrase );
54      }
55  
56  
57      /**
58       * Returns a DES symmetric key for the given input String components,
59       * which will be concatenated in the order described in RFC's 1510 and 3961,
60       * namely password+realm+username.
61       *
62       * @param password The password.
63       * @param realmName The name of the realm.
64       * @param userName The username.
65       * @return The derived symmetric DES key.
66       */
67      public byte[] getKey( String password, String realmName, String userName )
68      {
69          return generateKey( password + realmName + userName );
70      }
71  
72  
73      /**
74       * Returns a DES symmetric key for the given input String.
75       *
76       * @param passPhrase The passphrase.
77       * @return The DES key.
78       * @throws Exception
79       */
80      protected byte[] generateKey( String passPhrase )
81      {
82          byte encodedByteArray[] = characterEncodeString( passPhrase );
83  
84          byte paddedByteArray[] = padString( encodedByteArray );
85  
86          byte[] secretKey = fanFold( paddedByteArray );
87  
88          secretKey = setParity( secretKey );
89          secretKey = getStrongKey( secretKey );
90          secretKey = calculateChecksum( paddedByteArray, secretKey );
91          secretKey = setParity( secretKey );
92          secretKey = getStrongKey( secretKey );
93  
94          return secretKey;
95      }
96  
97  
98      /**
99       * 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 }