| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| package org.apache.directory.server.kerberos.shared.crypto.encryption; |
| |
| |
| import java.security.GeneralSecurityException; |
| import java.security.InvalidKeyException; |
| import java.security.spec.AlgorithmParameterSpec; |
| |
| import javax.crypto.Cipher; |
| import javax.crypto.SecretKey; |
| import javax.crypto.spec.DESKeySpec; |
| import javax.crypto.spec.IvParameterSpec; |
| import javax.crypto.spec.SecretKeySpec; |
| |
| import org.apache.directory.api.util.Strings; |
| |
| |
| /** |
| * An implementation of the DES string-to-key function as originally described |
| * in RFC 1510, "The Kerberos Network Authentication Service (V5)," and clarified |
| * in RFC 3961, "Encryption and Checksum Specifications for Kerberos 5." |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| */ |
| public class DesStringToKey |
| { |
| /** |
| * Returns a DES symmetric key for the given passphrase. |
| * |
| * @param passPhrase The passphrase to derive a symmetric DES key from. |
| * @return The derived symmetric DES key. |
| */ |
| public byte[] getKey( String passPhrase ) |
| { |
| return generateKey( passPhrase ); |
| } |
| |
| |
| /** |
| * Returns a DES symmetric key for the given input String components, |
| * which will be concatenated in the order described in RFC's 1510 and 3961, |
| * namely password+realm+username. |
| * |
| * @param password The password. |
| * @param realmName The name of the realm. |
| * @param userName The username. |
| * @return The derived symmetric DES key. |
| */ |
| public byte[] getKey( String password, String realmName, String userName ) |
| { |
| return generateKey( password + realmName + userName ); |
| } |
| |
| |
| /** |
| * Returns a DES symmetric key for the given input String. |
| * |
| * @param passPhrase The passphrase. |
| * @return The DES key. |
| * @throws Exception |
| */ |
| protected byte[] generateKey( String passPhrase ) |
| { |
| byte encodedByteArray[] = characterEncodeString( passPhrase ); |
| |
| byte paddedByteArray[] = padString( encodedByteArray ); |
| |
| byte[] secretKey = fanFold( paddedByteArray ); |
| |
| secretKey = setParity( secretKey ); |
| secretKey = getStrongKey( secretKey ); |
| secretKey = calculateChecksum( paddedByteArray, secretKey ); |
| secretKey = setParity( secretKey ); |
| secretKey = getStrongKey( secretKey ); |
| |
| return secretKey; |
| } |
| |
| |
| /** |
| * Set odd parity on an eight-byte array. |
| * |
| * @param in The byte array to set parity on. |
| * @return The parity-adjusted byte array. |
| */ |
| protected byte[] setParity( byte[] in ) |
| { |
| byte[] out = new byte[8]; |
| |
| int bitCount = 0; |
| int index = 0; |
| |
| for ( int i = 0; i < 64; i++ ) |
| { |
| if ( ( i + 1 ) % 8 == 0 ) |
| { |
| if ( bitCount % 2 == 0 ) |
| { |
| setBit( out, i, 1 ); |
| } |
| |
| index++; |
| bitCount = 0; |
| } |
| else |
| { |
| int val = getBit( in, index ); |
| boolean bit = val > 0; |
| |
| if ( bit ) |
| { |
| setBit( out, i, val ); |
| bitCount++; |
| } |
| |
| index++; |
| } |
| } |
| |
| return out; |
| } |
| |
| |
| /** |
| * Gets a bit at a given position. |
| * |
| * @param data |
| * @param pos |
| * @return The value of the bit. |
| */ |
| protected int getBit( byte[] data, int pos ) |
| { |
| int posByte = pos / 8; |
| int posBit = pos % 8; |
| |
| byte valByte = data[posByte]; |
| int valInt = valByte >> ( 8 - ( posBit + 1 ) ) & 0x0001; |
| return valInt; |
| } |
| |
| |
| /** |
| * Sets a bit at a given position. |
| * |
| * @param data |
| * @param pos |
| * @param val |
| */ |
| protected void setBit( byte[] data, int pos, int val ) |
| { |
| int posByte = pos / 8; |
| int posBit = pos % 8; |
| byte oldByte = data[posByte]; |
| oldByte = ( byte ) ( ( ( 0xFF7F >> posBit ) & oldByte ) & 0x00FF ); |
| byte newByte = ( byte ) ( ( val << ( 8 - ( posBit + 1 ) ) ) | oldByte ); |
| data[posByte] = newByte; |
| } |
| |
| |
| /** |
| * "The top bit of each octet (always zero if the password is plain |
| * ASCII, as was assumed when the original specification was written) is |
| * discarded, and the remaining seven bits of each octet form a |
| * bitstring. This is then fan-folded and eXclusive-ORed with itself to |
| * produce a 56-bit string. An eight-octet key is formed from this |
| * string, each octet using seven bits from the bitstring, leaving the |
| * least significant bit unassigned." |
| * |
| * @param paddedByteArray The padded byte array. |
| * @return The fan-folded intermediate DES key. |
| */ |
| protected byte[] fanFold( byte[] paddedByteArray ) |
| { |
| byte secretKey[] = new byte[8]; |
| |
| int div = paddedByteArray.length / 8; |
| |
| for ( int ii = 0; ii < div; ii++ ) |
| { |
| byte blockValue1[] = new byte[8]; |
| System.arraycopy( paddedByteArray, ii * 8, blockValue1, 0, 8 ); |
| |
| if ( ii % 2 == 1 ) |
| { |
| byte tempbyte1 = 0; |
| byte tempbyte2 = 0; |
| byte blockValue2[] = new byte[8]; |
| |
| for ( int jj = 0; jj < 8; jj++ ) |
| { |
| tempbyte2 = 0; |
| |
| for ( int kk = 0; kk < 4; kk++ ) |
| { |
| tempbyte2 = ( byte ) ( ( 1 << ( 7 - kk ) ) & 0xff ); |
| tempbyte1 |= ( blockValue1[jj] & tempbyte2 ) >>> ( 7 - 2 * kk ); |
| tempbyte2 = 0; |
| } |
| |
| for ( int kk = 4; kk < 8; kk++ ) |
| { |
| tempbyte2 = ( byte ) ( ( 1 << ( 7 - kk ) ) & 0xff ); |
| tempbyte1 |= ( blockValue1[jj] & tempbyte2 ) << ( 2 * kk - 7 ); |
| tempbyte2 = 0; |
| } |
| |
| blockValue2[7 - jj] = tempbyte1; |
| tempbyte1 = 0; |
| } |
| |
| for ( int jj = 0; jj < 8; jj++ ) |
| { |
| blockValue2[jj] = ( byte ) ( ( ( blockValue2[jj] & 0xff ) >>> 1 ) & 0xff ); |
| } |
| |
| System.arraycopy( blockValue2, 0, blockValue1, 0, blockValue2.length ); |
| } |
| |
| for ( int jj = 0; jj < 8; jj++ ) |
| { |
| blockValue1[jj] = ( byte ) ( ( ( blockValue1[jj] & 0xff ) << 1 ) & 0xff ); |
| } |
| |
| // ... eXclusive-ORed with itself to form an 8-byte DES key |
| for ( int jj = 0; jj < 8; jj++ ) |
| { |
| secretKey[jj] ^= blockValue1[jj]; |
| } |
| } |
| |
| return secretKey; |
| } |
| |
| |
| /** |
| * Calculates the checksum as described in "String or Random-Data to |
| * Key Transformation." An intermediate key is used to generate a DES CBC |
| * "checksum" on the initial passphrase+salt. The encryption key is also |
| * used as the IV. The final eight-byte block is returned as the "checksum." |
| * |
| * @param data The data to encrypt. |
| * @param keyBytes The bytes of the intermediate key. |
| * @return The final eight-byte block as the checksum. |
| */ |
| protected byte[] calculateChecksum( byte[] data, byte[] keyBytes ) |
| { |
| try |
| { |
| Cipher cipher = Cipher.getInstance( "DES/CBC/NoPadding" ); |
| SecretKey key = new SecretKeySpec( keyBytes, "DES" ); |
| |
| AlgorithmParameterSpec paramSpec = new IvParameterSpec( keyBytes ); |
| |
| cipher.init( Cipher.ENCRYPT_MODE, key, paramSpec ); |
| |
| byte[] result = cipher.doFinal( data ); |
| |
| byte[] checksum = new byte[8]; |
| System.arraycopy( result, result.length - 8, checksum, 0, 8 ); |
| |
| return checksum; |
| } |
| catch ( GeneralSecurityException nsae ) |
| { |
| nsae.printStackTrace(); |
| return null; |
| } |
| } |
| |
| |
| /** |
| * If the secret key is weak, correct by exclusive OR'ing |
| * with the constant 0xF0. |
| * |
| * @param secretKey The key to correct, if necessary. |
| * @return The corrected key. |
| */ |
| protected byte[] getStrongKey( byte[] secretKey ) |
| { |
| try |
| { |
| if ( DESKeySpec.isWeak( secretKey, 0 ) ) |
| { |
| secretKey[7] ^= 0xf0; |
| } |
| } |
| catch ( InvalidKeyException ike ) |
| { |
| return new byte[8]; |
| } |
| |
| return secretKey; |
| } |
| |
| |
| /** |
| * Encodes string with UTF-8 encoding. |
| * |
| * @param string The String to encode. |
| * @return The encoded String. |
| */ |
| protected byte[] characterEncodeString( String string ) |
| { |
| byte encodedByteArray[] = new byte[string.length()]; |
| |
| encodedByteArray = Strings.getBytesUtf8( string ); |
| |
| return encodedByteArray; |
| } |
| |
| |
| /** |
| * Add padding to make an exact multiple of 8 bytes. |
| * |
| * @param encodedString |
| * @return The padded byte array. |
| */ |
| protected byte[] padString( byte encodedString[] ) |
| { |
| int length; |
| |
| if ( encodedString.length < 8 ) |
| { |
| length = encodedString.length; |
| } |
| else |
| { |
| length = encodedString.length % 8; |
| } |
| |
| if ( length == 0 ) |
| { |
| return encodedString; |
| } |
| |
| byte paddedByteArray[] = new byte[( 8 - length ) + encodedString.length]; |
| |
| for ( int ii = paddedByteArray.length - 1; ii > encodedString.length - 1; ii-- ) |
| { |
| paddedByteArray[ii] = 0; |
| } |
| |
| System.arraycopy( encodedString, 0, paddedByteArray, 0, encodedString.length ); |
| |
| return paddedByteArray; |
| } |
| } |