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    }