1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 module hunt.shiro.authc.credential.HashedCredentialsMatcher; 20 21 import hunt.shiro.authc.credential.SimpleCredentialsMatcher; 22 23 import hunt.shiro.authc.AuthenticationInfo; 24 import hunt.shiro.authc.AuthenticationToken; 25 import hunt.shiro.authc.SaltedAuthenticationInfo; 26 import hunt.shiro.codec.Base64; 27 import hunt.shiro.codec.Hex; 28 import hunt.shiro.crypto.hash.AbstractHash; 29 import hunt.shiro.crypto.hash.Hash; 30 import hunt.shiro.crypto.hash.SimpleHash; 31 // import hunt.shiro.util.StringUtils; 32 33 import hunt.Exceptions; 34 import std.array; 35 36 /** 37 * A {@code HashedCredentialMatcher} provides support for hashing of supplied {@code AuthenticationToken} credentials 38 * before being compared to those in the {@code AuthenticationInfo} from the data store. 39 * <p/> 40 * Credential hashing is one of the most common security techniques when safeguarding a user's private credentials 41 * (passwords, keys, etc). Most developers never want to store their users' credentials in plain form, viewable by 42 * anyone, so they often hash the users' credentials before they are saved in the data store. 43 * <p/> 44 * This class (and its subclasses) function as follows: 45 * <ol> 46 * <li>Hash the {@code AuthenticationToken} credentials supplied by the user during their login.</li> 47 * <li>Compare this hashed value directly with the {@code AuthenticationInfo} credentials stored in the system 48 * (the stored account credentials are expected to already be in hashed form).</li> 49 * <li>If these two values are {@link #equals(Object, Object) equal}, the submitted credentials match, otherwise 50 * they do not.</li> 51 * </ol> 52 * <h2>Salting and Multiple Hash Iterations</h2> 53 * Because simple hashing is usually not good enough for secure applications, this class also supports 'salting' 54 * and multiple hash iterations. Please read this excellent 55 * <a href="http://www.owasp.org/index.php/Hashing_Java" _target="blank">Hashing Java article</a> to learn about 56 * salting and multiple iterations and why you might want to use them. (Note of sections 5 57 * "Why add salt?" and 6 "Hardening against the attacker's attack"). We should also note here that all of 58 * Shiro's Hash implementations (for example, {@link hunt.shiro.crypto.hash.Md5Hash Md5Hash}, 59 * {@link hunt.shiro.crypto.hash.Sha1Hash Sha1Hash}, etc) support salting and multiple hash iterations via 60 * overloaded constructors. 61 * <h4>Real World Case Study</h4> 62 * In April 2010, some public Atlassian Jira and Confluence 63 * installations (Apache Software Foundation, Codehaus, etc) were the target of account attacks and user accounts 64 * were compromised. The reason? Jira and Confluence at the time did not salt user passwords and attackers were 65 * able to use dictionary attacks to compromise user accounts (Atlassian has since 66 * <a href="http://blogs.atlassian.com/news/2010/04/oh_man_what_a_day_an_update_on_our_security_breach.html"> 67 * fixed the problem</a> of course). 68 * <p/> 69 * The lesson? 70 * <p/> 71 * <b>ALWAYS, ALWAYS, ALWAYS SALT USER PASSWORDS!</b> 72 * <p/> 73 * <h3>Salting</h3> 74 * Prior to Shiro 1.1, salts could be obtained based on the end-user submitted 75 * {@link AuthenticationToken AuthenticationToken} via the now-deprecated 76 * {@link #getSalt(hunt.shiro.authc.AuthenticationToken) getSalt(AuthenticationToken)} method. This however 77 * could constitute a security hole since ideally salts should never be obtained based on what a user can submit. 78 * User-submitted salt mechanisms are <em>much</em> more susceptible to dictionary attacks and <b>SHOULD NOT</b> be 79 * used in secure systems. Instead salts should ideally be a secure randomly-generated number that is generated when 80 * the user account is created. The secure number should never be disseminated to the user and always kept private 81 * by the application. 82 * <h4>Shiro 1.1</h4> 83 * As of Shiro 1.1, it is expected that any salt used to hash the submitted credentials will be obtained from the 84 * stored account information (represented as an {@link AuthenticationInfo AuthenticationInfo} instance). This is much 85 * more secure because the salt value remains private to the application (Shiro will never store this value). 86 * <p/> 87 * To enable this, {@code Realm}s should return {@link SaltedAuthenticationInfo SaltedAuthenticationInfo} instances 88 * during authentication. {@code HashedCredentialsMatcher} implementations will then use the provided 89 * {@link hunt.shiro.authc.SaltedAuthenticationInfo#getCredentialsSalt credentialsSalt} for hashing. To avoid 90 * security risks, 91 * <b>it is highly recommended that any existing {@code Realm} implementations that support hashed credentials are 92 * updated to return {@link SaltedAuthenticationInfo SaltedAuthenticationInfo} instances as soon as possible</b>. 93 * <h4>Shiro 1.0 Backwards Compatibility</h4> 94 * Because of the identified security risk, {@code Realm} implementations that support credentials hashing should 95 * be updated to return {@link SaltedAuthenticationInfo SaltedAuthenticationInfo} instances as 96 * soon as possible. 97 * <p/> 98 * If this is not possible for some reason, this class will retain 1.0 backwards-compatible behavior of obtaining 99 * the salt via the now-deprecated {@link #getSalt(AuthenticationToken) getSalt(AuthenticationToken)} method. This 100 * method will only be invoked if a {@code Realm} <em>does not</em> return 101 * {@link SaltedAuthenticationInfo SaltedAutenticationInfo} instances and {@link #isHashSalted() hashSalted} is 102 * {@code true}. 103 * But please note that the {@link #isHashSalted() hashSalted} property and the 104 * {@link #getSalt(AuthenticationToken) getSalt(AuthenticationToken)} methods will be removed before the Shiro 2.0 105 * release. 106 * <h3>Multiple Hash Iterations</h3> 107 * If you hash your users' credentials multiple times before persisting to the data store, you will also need to 108 * set this class's {@link #setHashIterations(int) hashIterations} property. See the 109 * <a href="http://www.owasp.org/index.php/Hashing_Java" _target="blank">Hashing Java article</a>'s 110 * <a href="http://www.owasp.org/index.php/Hashing_Java#Hardening_against_the_attacker.27s_attack"> 111 * "Hardening against the attacker's attack"</a> section to learn more about why you might want to use 112 * multiple hash iterations. 113 * <h2>MD5 & SHA-1 Notice</h2> 114 * <a href="http://en.wikipedia.org/wiki/MD5">MD5</a> and 115 * <a href="http://en.wikipedia.org/wiki/SHA_hash_functions">SHA-1</a> algorithms are now known to be vulnerable to 116 * compromise and/or collisions (read the linked pages for more). While most applications are ok with either of these 117 * two, if your application mandates high security, use the SHA-256 (or higher) hashing algorithms and their 118 * supporting {@code CredentialsMatcher} implementations. 119 * 120 * @see hunt.shiro.crypto.hash.Md5Hash 121 * @see hunt.shiro.crypto.hash.Sha1Hash 122 * @see hunt.shiro.crypto.hash.Sha256Hash 123 * @since 0.9 124 */ 125 class HashedCredentialsMatcher : SimpleCredentialsMatcher { 126 127 /** 128 */ 129 private string hashAlgorithm; 130 private int hashIterations; 131 private bool hashSalted; 132 private bool storedCredentialsHexEncoded; 133 134 /** 135 * JavaBeans-compatible no-arg constructor intended for use in IoC/Dependency Injection environments. If you 136 * use this constructor, you <em>MUST</em> also additionally set the 137 * {@link #setHashAlgorithmName(string) hashAlgorithmName} property. 138 */ 139 this() { 140 this.hashAlgorithm = null; 141 this.hashSalted = false; 142 this.hashIterations = 1; 143 this.storedCredentialsHexEncoded = true; //false means Base64-encoded 144 } 145 146 /** 147 * Creates an instance using the specified {@link #getHashAlgorithmName() hashAlgorithmName} to hash submitted 148 * credentials. 149 * @param hashAlgorithmName the {@code Hash} {@link hunt.shiro.crypto.hash.Hash#getAlgorithmName() algorithmName} 150 * to use when performing hashes for credentials matching. 151 */ 152 this(string hashAlgorithmName) { 153 this(); 154 if (hashAlgorithmName.empty()) { 155 throw new IllegalArgumentException("hashAlgorithmName cannot be null or empty."); 156 } 157 this.hashAlgorithm = hashAlgorithmName; 158 } 159 160 /** 161 * Returns the {@code Hash} {@link hunt.shiro.crypto.hash.Hash#getAlgorithmName() algorithmName} to use 162 * when performing hashes for credentials matching. 163 * 164 * @return the {@code Hash} {@link hunt.shiro.crypto.hash.Hash#getAlgorithmName() algorithmName} to use 165 * when performing hashes for credentials matching. 166 */ 167 string getHashAlgorithmName() { 168 return hashAlgorithm; 169 } 170 171 /** 172 * Sets the {@code Hash} {@link hunt.shiro.crypto.hash.Hash#getAlgorithmName() algorithmName} to use 173 * when performing hashes for credentials matching. 174 * 175 * @param hashAlgorithmName the {@code Hash} {@link hunt.shiro.crypto.hash.Hash#getAlgorithmName() algorithmName} 176 * to use when performing hashes for credentials matching. 177 */ 178 void setHashAlgorithmName(string hashAlgorithmName) { 179 this.hashAlgorithm = hashAlgorithmName; 180 } 181 182 /** 183 * Returns {@code true} if the system's stored credential hash is Hex encoded, {@code false} if it 184 * is Base64 encoded. 185 * <p/> 186 * Default value is {@code true} for convenience - all of Shiro's {@link Hash Hash#toString()} 187 * implementations return Hex encoded values by default, making this class's use with those implementations 188 * easier. 189 * 190 * @return {@code true} if the system's stored credential hash is Hex encoded, {@code false} if it 191 * is Base64 encoded. Default is {@code true} 192 */ 193 bool isStoredCredentialsHexEncoded() { 194 return storedCredentialsHexEncoded; 195 } 196 197 /** 198 * Sets the indicator if this system's stored credential hash is Hex encoded or not. 199 * <p/> 200 * A value of {@code true} will cause this class to decode the system credential from Hex, a 201 * value of {@code false} will cause this class to decode the system credential from Base64. 202 * <p/> 203 * Unless overridden via this method, the default value is {@code true} for convenience - all of Shiro's 204 * {@link Hash Hash#toString()} implementations return Hex encoded values by default, making this class's use with 205 * those implementations easier. 206 * 207 * @param storedCredentialsHexEncoded the indicator if this system's stored credential hash is Hex 208 * encoded or not ('not' automatically implying it is Base64 encoded). 209 */ 210 void setStoredCredentialsHexEncoded(bool storedCredentialsHexEncoded) { 211 this.storedCredentialsHexEncoded = storedCredentialsHexEncoded; 212 } 213 214 /** 215 * Returns {@code true} if a submitted {@code AuthenticationToken}'s credentials should be salted when hashing, 216 * {@code false} if it should not be salted. 217 * <p/> 218 * If enabled, the salt used will be obtained via the {@link #getSalt(AuthenticationToken) getSalt} method. 219 * <p/> 220 * The default value is {@code false}. 221 * 222 * @return {@code true} if a submitted {@code AuthenticationToken}'s credentials should be salted when hashing, 223 * {@code false} if it should not be salted. 224 * deprecated("") since Shiro 1.1. Hash salting is now expected to be based on if the {@link AuthenticationInfo} 225 * returned from the {@code Realm} is a {@link SaltedAuthenticationInfo} instance and its 226 * {@link hunt.shiro.authc.SaltedAuthenticationInfo#getCredentialsSalt() getCredentialsSalt()} method returns a non-null value. 227 * This method and the 1.0 behavior still exists for backwards compatibility if the {@code Realm} does not return 228 * {@code SaltedAuthenticationInfo} instances, but <b>it is highly recommended that {@code Realm} implementations 229 * that support hashed credentials start returning {@link SaltedAuthenticationInfo SaltedAuthenticationInfo} 230 * instances as soon as possible</b>. 231 * <p/> 232 * This is because salts should always be obtained from the stored account information and 233 * never be interpreted based on user/Subject-entered data. User-entered data is easier to compromise for 234 * attackers, whereas account-unique (and secure randomly-generated) salts never disseminated to the end-user 235 * are almost impossible to break. This method will be removed in Shiro 2.0. 236 */ 237 deprecated("") 238 bool isHashSalted() { 239 return hashSalted; 240 } 241 242 /** 243 * Sets whether or not to salt a submitted {@code AuthenticationToken}'s credentials when hashing. 244 * <p/> 245 * If enabled, the salt used will be obtained via the {@link #getSalt(hunt.shiro.authc.AuthenticationToken) getCredentialsSalt} method. 246 * </p> 247 * The default value is {@code false}. 248 * 249 * @param hashSalted whether or not to salt a submitted {@code AuthenticationToken}'s credentials when hashing. 250 * deprecated("") since Shiro 1.1. Hash salting is now expected to be based on if the {@link AuthenticationInfo} 251 * returned from the {@code Realm} is a {@link SaltedAuthenticationInfo} instance and its 252 * {@link hunt.shiro.authc.SaltedAuthenticationInfo#getCredentialsSalt() getCredentialsSalt()} method returns a non-null value. 253 * This method and the 1.0 behavior still exists for backwards compatibility if the {@code Realm} does not return 254 * {@code SaltedAuthenticationInfo} instances, but <b>it is highly recommended that {@code Realm} implementations 255 * that support hashed credentials start returning {@link SaltedAuthenticationInfo SaltedAuthenticationInfo} 256 * instances as soon as possible</b>. 257 * <p/> 258 * This is because salts should always be obtained from the stored account information and 259 * never be interpreted based on user/Subject-entered data. User-entered data is easier to compromise for 260 * attackers, whereas account-unique (and secure randomly-generated) salts never disseminated to the end-user 261 * are almost impossible to break. This method will be removed in Shiro 2.0. 262 */ 263 deprecated("") 264 void setHashSalted(bool hashSalted) { 265 this.hashSalted = hashSalted; 266 } 267 268 /** 269 * Returns the number of times a submitted {@code AuthenticationToken}'s credentials will be hashed before 270 * comparing to the credentials stored in the system. 271 * <p/> 272 * Unless overridden, the default value is {@code 1}, meaning a normal hash execution will occur. 273 * 274 * @return the number of times a submitted {@code AuthenticationToken}'s credentials will be hashed before 275 * comparing to the credentials stored in the system. 276 */ 277 int getHashIterations() { 278 return hashIterations; 279 } 280 281 /** 282 * Sets the number of times a submitted {@code AuthenticationToken}'s credentials will be hashed before comparing 283 * to the credentials stored in the system. 284 * <p/> 285 * Unless overridden, the default value is {@code 1}, meaning a normal single hash execution will occur. 286 * <p/> 287 * If this argument is less than 1 (i.e. 0 or negative), the default value of 1 is applied. There must always be 288 * at least 1 hash iteration (otherwise there would be no hash). 289 * 290 * @param hashIterations the number of times to hash a submitted {@code AuthenticationToken}'s credentials. 291 */ 292 void setHashIterations(int hashIterations) { 293 if (hashIterations < 1) { 294 this.hashIterations = 1; 295 } else { 296 this.hashIterations = hashIterations; 297 } 298 } 299 300 /** 301 * Returns a salt value used to hash the token's credentials. 302 * <p/> 303 * This default implementation merely returns {@code token.getPrincipal()}, effectively using the user's 304 * identity (username, user id, etc) as the salt, a most common technique. If you wish to provide the 305 * authentication token's salt another way, you may override this method. 306 * 307 * @param token the AuthenticationToken submitted during the authentication attempt. 308 * @return a salt value to use to hash the authentication token's credentials. 309 * deprecated("") since Shiro 1.1. Hash salting is now expected to be based on if the {@link AuthenticationInfo} 310 * returned from the {@code Realm} is a {@link SaltedAuthenticationInfo} instance and its 311 * {@link hunt.shiro.authc.SaltedAuthenticationInfo#getCredentialsSalt() getCredentialsSalt()} method returns a non-null value. 312 * This method and the 1.0 behavior still exists for backwards compatibility if the {@code Realm} does not return 313 * {@code SaltedAuthenticationInfo} instances, but <b>it is highly recommended that {@code Realm} implementations 314 * that support hashed credentials start returning {@link SaltedAuthenticationInfo SaltedAuthenticationInfo} 315 * instances as soon as possible</b>.<p/> 316 * This is because salts should always be obtained from the stored account information and 317 * never be interpreted based on user/Subject-entered data. User-entered data is easier to compromise for 318 * attackers, whereas account-unique (and secure randomly-generated) salts never disseminated to the end-user 319 * are almost impossible to break. This method will be removed in Shiro 2.0. 320 */ 321 deprecated("") 322 protected string getSalt(AuthenticationToken token) { 323 return token.getPrincipal(); 324 } 325 326 /** 327 * Returns a {@link Hash Hash} instance representing the already-hashed AuthenticationInfo credentials stored in the system. 328 * <p/> 329 * This method reconstructs a {@link Hash Hash} instance based on a {@code info.getCredentials} call, 330 * but it does <em>not</em> hash that value - it is expected that method call will return an already-hashed value. 331 * <p/> 332 * This implementation's reconstruction effort functions as follows: 333 * <ol> 334 * <li>Convert {@code account.getCredentials()} to a byte array via the {@link #toBytes toBytes} method. 335 * <li>If {@code account.getCredentials()} was originally a string or[] char before {@code toBytes} was 336 * called, check for encoding: 337 * <li>If {@link #storedCredentialsHexEncoded storedCredentialsHexEncoded}, Hex decode that byte array, otherwise 338 * Base64 decode the byte array</li> 339 * <li>Set the[] byte array directly on the {@code Hash} implementation and return it.</li> 340 * </ol> 341 * 342 * @param info the AuthenticationInfo from which to retrieve the credentials which assumed to be in already-hashed form. 343 * @return a {@link Hash Hash} instance representing the given AuthenticationInfo's stored credentials. 344 */ 345 // protected Object getCredentials(AuthenticationInfo info) { 346 // Object credentials = info.getCredentials(); 347 348 // byte[] storedBytes = toBytes(credentials); 349 // auto credentialsCast = cast(string) credentials; 350 // if (credentialsCast !is null || credentials instanceof[] char) { 351 // //account.credentials were a[] char or string, so 352 // //we need to do text decoding first: 353 // if (isStoredCredentialsHexEncoded()) { 354 // storedBytes = Hex.decode(storedBytes); 355 // } else { 356 // storedBytes = Base64.decode(storedBytes); 357 // } 358 // } 359 // AbstractHash hash = newHashInstance(); 360 // hash.setBytes(storedBytes); 361 // return hash; 362 // } 363 364 /** 365 * This implementation first hashes the {@code token}'s credentials, potentially using a 366 * {@code salt} if the {@code info} argument is a 367 * {@link hunt.shiro.authc.SaltedAuthenticationInfo SaltedAuthenticationInfo}. It then compares the hash 368 * against the {@code AuthenticationInfo}'s 369 * {@link #getCredentials(hunt.shiro.authc.AuthenticationInfo) already-hashed credentials}. This method 370 * returns {@code true} if those two values are {@link #equals(Object, Object) equal}, {@code false} otherwise. 371 * 372 * @param token the {@code AuthenticationToken} submitted during the authentication attempt. 373 * @param info the {@code AuthenticationInfo} stored in the system matching the token principal 374 * @return {@code true} if the provided token credentials hash match to the stored account credentials hash, 375 * {@code false} otherwise 376 * @since 1.1 377 */ 378 override 379 bool doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { 380 Object tokenHashedCredentials = hashProvidedCredentials(token, info); 381 Object accountCredentials = getCredentials(info); 382 return equals(tokenHashedCredentials, accountCredentials); 383 } 384 385 /** 386 * Hash the provided {@code token}'s credentials using the salt stored with the account if the 387 * {@code info} instance is an {@code instanceof} {@link SaltedAuthenticationInfo SaltedAuthenticationInfo} (see 388 * the class-level JavaDoc for why this is the preferred approach). 389 * <p/> 390 * If the {@code info} instance is <em>not</em> 391 * an {@code instanceof} {@code SaltedAuthenticationInfo}, the logic will fall back to Shiro 1.0 392 * backwards-compatible logic: it will first check to see {@link #isHashSalted() isHashSalted} and if so, will try 393 * to acquire the salt from {@link #getSalt(AuthenticationToken) getSalt(AuthenticationToken)}. See the class-level 394 * JavaDoc for why this is not recommended. This 'fallback' logic exists only for backwards-compatibility. 395 * {@code Realm}s should be updated as soon as possible to return {@code SaltedAuthenticationInfo} instances 396 * if account credentials salting is enabled (highly recommended for password-based systems). 397 * 398 * @param token the submitted authentication token from which its credentials will be hashed 399 * @param info the stored account data, potentially used to acquire a salt 400 * @return the token credentials hash 401 * @since 1.1 402 */ 403 protected Object hashProvidedCredentials(AuthenticationToken token, AuthenticationInfo info) { 404 // Object salt = null; 405 // SaltedAuthenticationInfo infoCast = cast(SaltedAuthenticationInfo)info; 406 // if (infoCast !is null) { 407 // salt = cast(Object)infoCast.getCredentialsSalt(); 408 // } else { 409 // //retain 1.0 backwards compatibility: 410 // if (isHashSalted()) { 411 // salt = getSalt(token); 412 // } 413 // } 414 // return cast(Object)hashProvidedCredentials(token.getCredentials(), salt, getHashIterations()); 415 416 implementationMissing(false); 417 return null; 418 } 419 420 /** 421 * Returns the {@link #getHashAlgorithmName() hashAlgorithmName} property, but will throw an 422 * {@link IllegalStateException} if it has not been set. 423 * 424 * @return the required {@link #getHashAlgorithmName() hashAlgorithmName} property 425 * @throws IllegalStateException if the property has not been set prior to calling this method. 426 */ 427 private string assertHashAlgorithmName(){ 428 string hashAlgorithmName = getHashAlgorithmName(); 429 if (hashAlgorithmName is null) { 430 string msg = "Required 'hashAlgorithmName' property has not been set. This is required to execute " ~ 431 "the hashing algorithm."; 432 throw new IllegalStateException(msg); 433 } 434 return hashAlgorithmName; 435 } 436 437 /** 438 * Hashes the provided credentials a total of {@code hashIterations} times, using the given salt. The hash 439 * implementation/algorithm used is based on the {@link #getHashAlgorithmName() hashAlgorithmName} property. 440 * 441 * @param credentials the submitted authentication token's credentials to hash 442 * @param salt the value to salt the hash, or {@code null} if a salt will not be used. 443 * @param hashIterations the number of times to hash the credentials. At least one hash will always occur though, 444 * even if this argument is 0 or negative. 445 * @return the hashed value of the provided credentials, according to the specified salt and hash iterations. 446 */ 447 protected Hash hashProvidedCredentials(Object credentials, Object salt, int hashIterations) { 448 string hashAlgorithmName = assertHashAlgorithmName(); 449 return new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations); 450 } 451 452 /** 453 * Returns a new, <em>uninitialized</em> instance, without its byte array set. Used as a utility method in the 454 * {@link SimpleCredentialsMatcher#getCredentials(hunt.shiro.authc.AuthenticationInfo) getCredentials(AuthenticationInfo)} implementation. 455 * 456 * @return a new, <em>uninitialized</em> instance, without its byte array set. 457 */ 458 protected AbstractHash newHashInstance() { 459 string hashAlgorithmName = assertHashAlgorithmName(); 460 return new SimpleHash(hashAlgorithmName); 461 } 462 463 }