Coverage Report - org.apache.shiro.authc.credential.DefaultPasswordService
 
Classes in this File Line Coverage Branch Coverage Complexity
DefaultPasswordService
100%
60/60
71%
27/38
2.533
 
 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  
 package org.apache.shiro.authc.credential;
 20  
 
 21  
 import org.apache.shiro.crypto.hash.DefaultHashService;
 22  
 import org.apache.shiro.crypto.hash.Hash;
 23  
 import org.apache.shiro.crypto.hash.HashRequest;
 24  
 import org.apache.shiro.crypto.hash.HashService;
 25  
 import org.apache.shiro.crypto.hash.format.*;
 26  
 import org.apache.shiro.util.ByteSource;
 27  
 import org.slf4j.Logger;
 28  
 import org.slf4j.LoggerFactory;
 29  
 
 30  
 /**
 31  
  * Default implementation of the {@link PasswordService} interface that relies on an internal
 32  
  * {@link HashService}, {@link HashFormat}, and {@link HashFormatFactory} to function:
 33  
  * <h2>Hashing Passwords</h2>
 34  
  *
 35  
  * <h2>Comparing Passwords</h2>
 36  
  * All hashing operations are performed by the internal {@link #getHashService() hashService}.  After the hash
 37  
  * is computed, it is formatted into a String value via the internal {@link #getHashFormat() hashFormat}.
 38  
  *
 39  
  * @since 1.2
 40  
  */
 41  
 public class DefaultPasswordService implements HashingPasswordService {
 42  
 
 43  
     public static final String DEFAULT_HASH_ALGORITHM = "SHA-256";
 44  
     public static final int DEFAULT_HASH_ITERATIONS = 500000; //500,000
 45  
 
 46  1
     private static final Logger log = LoggerFactory.getLogger(DefaultPasswordService.class);
 47  
 
 48  
     private HashService hashService;
 49  
     private HashFormat hashFormat;
 50  
     private HashFormatFactory hashFormatFactory;
 51  
 
 52  
     private volatile boolean hashFormatWarned; //used to avoid excessive log noise
 53  
 
 54  16
     public DefaultPasswordService() {
 55  16
         this.hashFormatWarned = false;
 56  
 
 57  16
         DefaultHashService hashService = new DefaultHashService();
 58  16
         hashService.setHashAlgorithmName(DEFAULT_HASH_ALGORITHM);
 59  16
         hashService.setHashIterations(DEFAULT_HASH_ITERATIONS);
 60  16
         hashService.setGeneratePublicSalt(true); //always want generated salts for user passwords to be most secure
 61  16
         this.hashService = hashService;
 62  
 
 63  16
         this.hashFormat = new Shiro1CryptFormat();
 64  16
         this.hashFormatFactory = new DefaultHashFormatFactory();
 65  16
     }
 66  
 
 67  
     public String encryptPassword(Object plaintext) {
 68  8
         Hash hash = hashPassword(plaintext);
 69  8
         checkHashFormatDurability();
 70  8
         return this.hashFormat.format(hash);
 71  
     }
 72  
 
 73  
     public Hash hashPassword(Object plaintext) {
 74  9
         ByteSource plaintextBytes = createByteSource(plaintext);
 75  9
         if (plaintextBytes == null || plaintextBytes.isEmpty()) {
 76  2
             return null;
 77  
         }
 78  7
         HashRequest request = createHashRequest(plaintextBytes);
 79  7
         return hashService.computeHash(request);
 80  
     }
 81  
 
 82  
     public boolean passwordsMatch(Object plaintext, Hash saved) {
 83  7
         ByteSource plaintextBytes = createByteSource(plaintext);
 84  
 
 85  7
         if (saved == null || saved.isEmpty()) {
 86  1
             return plaintextBytes == null || plaintextBytes.isEmpty();
 87  
         } else {
 88  6
             if (plaintextBytes == null || plaintextBytes.isEmpty()) {
 89  1
                 return false;
 90  
             }
 91  
         }
 92  
 
 93  5
         HashRequest request = buildHashRequest(plaintextBytes, saved);
 94  
 
 95  5
         Hash computed = this.hashService.computeHash(request);
 96  
 
 97  5
         return saved.equals(computed);
 98  
     }
 99  
 
 100  
     protected void checkHashFormatDurability() {
 101  
 
 102  8
         if (!this.hashFormatWarned) {
 103  
 
 104  8
             HashFormat format = this.hashFormat;
 105  
 
 106  8
             if (!(format instanceof ParsableHashFormat) && log.isWarnEnabled()) {
 107  2
                 String msg = "The configured hashFormat instance [" + format.getClass().getName() + "] is not a " +
 108  
                         ParsableHashFormat.class.getName() + " implementation.  This is " +
 109  
                         "required if you wish to support backwards compatibility for saved password checking (almost " +
 110  
                         "always desirable).  Without a " + ParsableHashFormat.class.getSimpleName() + " instance, " +
 111  
                         "any hashService configuration changes will break previously hashed/saved passwords.";
 112  2
                 log.warn(msg);
 113  2
                 this.hashFormatWarned = true;
 114  
             }
 115  
         }
 116  8
     }
 117  
 
 118  
     protected HashRequest createHashRequest(ByteSource plaintext) {
 119  8
         return new HashRequest.Builder().setSource(plaintext).build();
 120  
     }
 121  
 
 122  
     protected ByteSource createByteSource(Object o) {
 123  26
         return ByteSource.Util.bytes(o);
 124  
     }
 125  
 
 126  
     public boolean passwordsMatch(Object submittedPlaintext, String saved) {
 127  10
         ByteSource plaintextBytes = createByteSource(submittedPlaintext);
 128  
 
 129  10
         if (saved == null || saved.length() == 0) {
 130  3
             return plaintextBytes == null || plaintextBytes.isEmpty();
 131  
         } else {
 132  7
             if (plaintextBytes == null || plaintextBytes.isEmpty()) {
 133  1
                 return false;
 134  
             }
 135  
         }
 136  
 
 137  
         //First check to see if we can reconstitute the original hash - this allows us to
 138  
         //perform password hash comparisons even for previously saved passwords that don't
 139  
         //match the current HashService configuration values.  This is a very nice feature
 140  
         //for password comparisons because it ensures backwards compatibility even after
 141  
         //configuration changes.
 142  6
         HashFormat discoveredFormat = this.hashFormatFactory.getInstance(saved);
 143  
 
 144  6
         if (discoveredFormat != null && discoveredFormat instanceof ParsableHashFormat) {
 145  
 
 146  5
             ParsableHashFormat parsableHashFormat = (ParsableHashFormat)discoveredFormat;
 147  5
             Hash savedHash = parsableHashFormat.parse(saved);
 148  
 
 149  5
             return passwordsMatch(submittedPlaintext, savedHash);
 150  
         }
 151  
 
 152  
         //If we're at this point in the method's execution, We couldn't reconstitute the original hash.
 153  
         //So, we need to hash the submittedPlaintext using current HashService configuration and then
 154  
         //compare the formatted output with the saved string.  This will correctly compare passwords,
 155  
         //but does not allow changing the HashService configuration without breaking previously saved
 156  
         //passwords:
 157  
 
 158  
         //The saved text value can't be reconstituted into a Hash instance.  We need to format the
 159  
         //submittedPlaintext and then compare this formatted value with the saved value:
 160  1
         HashRequest request = createHashRequest(plaintextBytes);
 161  1
         Hash computed = this.hashService.computeHash(request);
 162  1
         String formatted = this.hashFormat.format(computed);
 163  
 
 164  1
         return saved.equals(formatted);
 165  
     }
 166  
 
 167  
     protected HashRequest buildHashRequest(ByteSource plaintext, Hash saved) {
 168  
         //keep everything from the saved hash except for the source:
 169  5
         return new HashRequest.Builder().setSource(plaintext)
 170  
                 //now use the existing saved data:
 171  
                 .setAlgorithmName(saved.getAlgorithmName())
 172  
                 .setSalt(saved.getSalt())
 173  
                 .setIterations(saved.getIterations())
 174  
                 .build();
 175  
     }
 176  
 
 177  
     public HashService getHashService() {
 178  3
         return hashService;
 179  
     }
 180  
 
 181  
     public void setHashService(HashService hashService) {
 182  1
         this.hashService = hashService;
 183  1
     }
 184  
 
 185  
     public HashFormat getHashFormat() {
 186  1
         return hashFormat;
 187  
     }
 188  
 
 189  
     public void setHashFormat(HashFormat hashFormat) {
 190  2
         this.hashFormat = hashFormat;
 191  2
     }
 192  
 
 193  
     public HashFormatFactory getHashFormatFactory() {
 194  1
         return hashFormatFactory;
 195  
     }
 196  
 
 197  
     public void setHashFormatFactory(HashFormatFactory hashFormatFactory) {
 198  1
         this.hashFormatFactory = hashFormatFactory;
 199  1
     }
 200  
 }