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 */
020package org.apache.directory.server.kerberos.shared.crypto.encryption;
021
022
023import java.security.GeneralSecurityException;
024import java.security.InvalidKeyException;
025import java.security.spec.AlgorithmParameterSpec;
026
027import javax.crypto.Cipher;
028import javax.crypto.SecretKey;
029import javax.crypto.spec.DESKeySpec;
030import javax.crypto.spec.IvParameterSpec;
031import javax.crypto.spec.SecretKeySpec;
032
033import org.apache.directory.api.util.Strings;
034
035
036/**
037 * An implementation of the DES string-to-key function as originally described
038 * in RFC 1510, "The Kerberos Network Authentication Service (V5)," and clarified
039 * in RFC 3961, "Encryption and Checksum Specifications for Kerberos 5."
040 *
041 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
042 */
043public 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     */
079    protected byte[] generateKey( String passPhrase )
080    {
081        byte encodedByteArray[] = characterEncodeString( passPhrase );
082
083        byte paddedByteArray[] = padString( encodedByteArray );
084
085        byte[] secretKey = fanFold( paddedByteArray );
086
087        secretKey = setParity( secretKey );
088        secretKey = getStrongKey( secretKey );
089        secretKey = calculateChecksum( paddedByteArray, secretKey );
090        secretKey = setParity( secretKey );
091        secretKey = getStrongKey( secretKey );
092
093        return secretKey;
094    }
095
096
097    /**
098     * Set odd parity on an eight-byte array.
099     *
100     * @param in The byte array to set parity on.
101     * @return The parity-adjusted byte array.
102     */
103    protected byte[] setParity( byte[] in )
104    {
105        byte[] out = new byte[8];
106
107        int bitCount = 0;
108        int index = 0;
109
110        for ( int i = 0; i < 64; i++ )
111        {
112            if ( ( i + 1 ) % 8 == 0 )
113            {
114                if ( bitCount % 2 == 0 )
115                {
116                    setBit( out, i, 1 );
117                }
118
119                index++;
120                bitCount = 0;
121            }
122            else
123            {
124                int val = getBit( in, index );
125                boolean bit = val > 0;
126
127                if ( bit )
128                {
129                    setBit( out, i, val );
130                    bitCount++;
131                }
132
133                index++;
134            }
135        }
136
137        return out;
138    }
139
140
141    /**
142     * Gets a bit at a given position.
143     *
144     * @param data
145     * @param pos
146     * @return The value of the bit.
147     */
148    protected int getBit( byte[] data, int pos )
149    {
150        int posByte = pos / 8;
151        int posBit = pos % 8;
152
153        byte valByte = data[posByte];
154        
155        return valByte >> ( 8 - ( posBit + 1 ) ) & 0x0001;
156    }
157
158
159    /**
160     * Sets a bit at a given position.
161     *
162     * @param data
163     * @param pos
164     * @param val
165     */
166    protected void setBit( byte[] data, int pos, int val )
167    {
168        int posByte = pos / 8;
169        int posBit = pos % 8;
170        byte oldByte = data[posByte];
171        oldByte = ( byte ) ( ( ( 0xFF7F >> posBit ) & oldByte ) & 0x00FF );
172        byte newByte = ( byte ) ( ( val << ( 8 - ( posBit + 1 ) ) ) | oldByte );
173        data[posByte] = newByte;
174    }
175
176
177    /**
178     * "The top bit of each octet (always zero if the password is plain
179     * ASCII, as was assumed when the original specification was written) is
180     * discarded, and the remaining seven bits of each octet form a
181     * bitstring.  This is then fan-folded and eXclusive-ORed with itself to
182     * produce a 56-bit string.  An eight-octet key is formed from this
183     * string, each octet using seven bits from the bitstring, leaving the
184     * least significant bit unassigned."
185     *
186     * @param paddedByteArray The padded byte array.
187     * @return The fan-folded intermediate DES key.
188     */
189    protected byte[] fanFold( byte[] paddedByteArray )
190    {
191        byte secretKey[] = new byte[8];
192
193        int div = paddedByteArray.length / 8;
194
195        for ( int ii = 0; ii < div; ii++ )
196        {
197            byte blockValue1[] = new byte[8];
198            System.arraycopy( paddedByteArray, ii * 8, blockValue1, 0, 8 );
199
200            if ( ii % 2 == 1 )
201            {
202                byte tempbyte1 = 0;
203                byte tempbyte2 = 0;
204                byte blockValue2[] = new byte[8];
205
206                for ( int jj = 0; jj < 8; jj++ )
207                {
208                    for ( int kk = 0; kk < 4; kk++ )
209                    {
210                        tempbyte2 = ( byte ) ( ( 1 << ( 7 - kk ) ) & 0xff );
211                        tempbyte1 |= ( blockValue1[jj] & tempbyte2 ) >>> ( 7 - 2 * kk );
212                    }
213
214                    for ( int kk = 4; kk < 8; kk++ )
215                    {
216                        tempbyte2 = ( byte ) ( ( 1 << ( 7 - kk ) ) & 0xff );
217                        tempbyte1 |= ( blockValue1[jj] & tempbyte2 ) << ( 2 * kk - 7 );
218                    }
219
220                    blockValue2[7 - jj] = tempbyte1;
221                    tempbyte1 = 0;
222                }
223
224                for ( int jj = 0; jj < 8; jj++ )
225                {
226                    blockValue2[jj] = ( byte ) ( ( ( blockValue2[jj] & 0xff ) >>> 1 ) & 0xff );
227                }
228
229                System.arraycopy( blockValue2, 0, blockValue1, 0, blockValue2.length );
230            }
231
232            for ( int jj = 0; jj < 8; jj++ )
233            {
234                blockValue1[jj] = ( byte ) ( ( ( blockValue1[jj] & 0xff ) << 1 ) & 0xff );
235            }
236
237            // ... eXclusive-ORed with itself to form an 8-byte DES key
238            for ( int jj = 0; jj < 8; jj++ )
239            {
240                secretKey[jj] ^= blockValue1[jj];
241            }
242        }
243
244        return secretKey;
245    }
246
247
248    /**
249     * Calculates the checksum as described in "String or Random-Data to
250     * Key Transformation."  An intermediate key is used to generate a DES CBC
251     * "checksum" on the initial passphrase+salt.  The encryption key is also
252     * used as the IV.  The final eight-byte block is returned as the "checksum."
253     *
254     * @param data The data to encrypt.
255     * @param keyBytes The bytes of the intermediate key.
256     * @return The final eight-byte block as the checksum.
257     */
258    protected byte[] calculateChecksum( byte[] data, byte[] keyBytes )
259    {
260        try
261        {
262            Cipher cipher = Cipher.getInstance( "DES/CBC/NoPadding" );
263            SecretKey key = new SecretKeySpec( keyBytes, "DES" );
264
265            AlgorithmParameterSpec paramSpec = new IvParameterSpec( keyBytes );
266
267            cipher.init( Cipher.ENCRYPT_MODE, key, paramSpec );
268
269            byte[] result = cipher.doFinal( data );
270
271            byte[] checksum = new byte[8];
272            System.arraycopy( result, result.length - 8, checksum, 0, 8 );
273
274            return checksum;
275        }
276        catch ( GeneralSecurityException nsae )
277        {
278            nsae.printStackTrace();
279            return null;
280        }
281    }
282
283
284    /**
285     * If the secret key is weak, correct by exclusive OR'ing
286     * with the constant 0xF0.
287     *
288     * @param secretKey The key to correct, if necessary.
289     * @return The corrected key.
290     */
291    protected byte[] getStrongKey( byte[] secretKey )
292    {
293        try
294        {
295            if ( DESKeySpec.isWeak( secretKey, 0 ) )
296            {
297                secretKey[7] ^= 0xf0;
298            }
299        }
300        catch ( InvalidKeyException ike )
301        {
302            return new byte[8];
303        }
304
305        return secretKey;
306    }
307
308
309    /**
310     * Encodes string with UTF-8 encoding.
311     *
312     * @param string The String to encode.
313     * @return The encoded String.
314     */
315    protected byte[] characterEncodeString( String string )
316    {
317        return Strings.getBytesUtf8( string );
318    }
319
320
321    /**
322     * Add padding to make an exact multiple of 8 bytes.
323     *
324     * @param encodedString
325     * @return The padded byte array.
326     */
327    protected byte[] padString( byte encodedString[] )
328    {
329        int length;
330
331        if ( encodedString.length < 8 )
332        {
333            length = encodedString.length;
334        }
335        else
336        {
337            length = encodedString.length % 8;
338        }
339
340        if ( length == 0 )
341        {
342            return encodedString;
343        }
344
345        byte paddedByteArray[] = new byte[( 8 - length ) + encodedString.length];
346
347        for ( int ii = paddedByteArray.length - 1; ii > encodedString.length - 1; ii-- )
348        {
349            paddedByteArray[ii] = 0;
350        }
351
352        System.arraycopy( encodedString, 0, paddedByteArray, 0, encodedString.length );
353
354        return paddedByteArray;
355    }
356}