blob: 0f6274a3b13e15c28d73390f9225a6469defed13 [file] [log] [blame]
/*
* 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;
}
}