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 }