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}