1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.shiro.authc.credential;
20
21 import java.security.MessageDigest;
22
23 import org.apache.shiro.crypto.hash.DefaultHashService;
24 import org.apache.shiro.crypto.hash.Hash;
25 import org.apache.shiro.crypto.hash.HashRequest;
26 import org.apache.shiro.crypto.hash.HashService;
27 import org.apache.shiro.crypto.hash.format.*;
28 import org.apache.shiro.util.ByteSource;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32
33
34
35
36
37
38
39
40
41
42
43 public class DefaultPasswordService implements HashingPasswordService {
44
45 public static final String DEFAULT_HASH_ALGORITHM = "SHA-256";
46 public static final int DEFAULT_HASH_ITERATIONS = 500000;
47
48 private static final Logger log = LoggerFactory.getLogger(DefaultPasswordService.class);
49
50 private HashService hashService;
51 private HashFormat hashFormat;
52 private HashFormatFactory hashFormatFactory;
53
54 private volatile boolean hashFormatWarned;
55
56 public DefaultPasswordService() {
57 this.hashFormatWarned = false;
58
59 DefaultHashService hashService = new DefaultHashService();
60 hashService.setHashAlgorithmName(DEFAULT_HASH_ALGORITHM);
61 hashService.setHashIterations(DEFAULT_HASH_ITERATIONS);
62 hashService.setGeneratePublicSalt(true);
63 this.hashService = hashService;
64
65 this.hashFormat = new Shiro1CryptFormat();
66 this.hashFormatFactory = new DefaultHashFormatFactory();
67 }
68
69 public String encryptPassword(Object plaintext) {
70 Hash hash = hashPassword(plaintext);
71 checkHashFormatDurability();
72 return this.hashFormat.format(hash);
73 }
74
75 public Hash hashPassword(Object plaintext) {
76 ByteSource plaintextBytes = createByteSource(plaintext);
77 if (plaintextBytes == null || plaintextBytes.isEmpty()) {
78 return null;
79 }
80 HashRequest request = createHashRequest(plaintextBytes);
81 return hashService.computeHash(request);
82 }
83
84 public boolean passwordsMatch(Object plaintext, Hash saved) {
85 ByteSource plaintextBytes = createByteSource(plaintext);
86
87 if (saved == null || saved.isEmpty()) {
88 return plaintextBytes == null || plaintextBytes.isEmpty();
89 } else {
90 if (plaintextBytes == null || plaintextBytes.isEmpty()) {
91 return false;
92 }
93 }
94
95 HashRequest request = buildHashRequest(plaintextBytes, saved);
96
97 Hash computed = this.hashService.computeHash(request);
98
99 return constantEquals(saved.toString(), computed.toString());
100 }
101
102 private boolean constantEquals(String savedHash, String computedHash) {
103
104 byte[] savedHashByteArray = savedHash.getBytes();
105 byte[] computedHashByteArray = computedHash.getBytes();
106
107 return MessageDigest.isEqual(savedHashByteArray, computedHashByteArray);
108 }
109
110 protected void checkHashFormatDurability() {
111
112 if (!this.hashFormatWarned) {
113
114 HashFormat format = this.hashFormat;
115
116 if (!(format instanceof ParsableHashFormat) && log.isWarnEnabled()) {
117 String msg = "The configured hashFormat instance [" + format.getClass().getName() + "] is not a " +
118 ParsableHashFormat.class.getName() + " implementation. This is " +
119 "required if you wish to support backwards compatibility for saved password checking (almost " +
120 "always desirable). Without a " + ParsableHashFormat.class.getSimpleName() + " instance, " +
121 "any hashService configuration changes will break previously hashed/saved passwords.";
122 log.warn(msg);
123 this.hashFormatWarned = true;
124 }
125 }
126 }
127
128 protected HashRequest createHashRequest(ByteSource plaintext) {
129 return new HashRequest.Builder().setSource(plaintext).build();
130 }
131
132 protected ByteSource createByteSource(Object o) {
133 return ByteSource.Util.bytes(o);
134 }
135
136 public boolean passwordsMatch(Object submittedPlaintext, String saved) {
137 ByteSource plaintextBytes = createByteSource(submittedPlaintext);
138
139 if (saved == null || saved.length() == 0) {
140 return plaintextBytes == null || plaintextBytes.isEmpty();
141 } else {
142 if (plaintextBytes == null || plaintextBytes.isEmpty()) {
143 return false;
144 }
145 }
146
147
148
149
150
151
152 HashFormat discoveredFormat = this.hashFormatFactory.getInstance(saved);
153
154 if (discoveredFormat != null && discoveredFormat instanceof ParsableHashFormat) {
155
156 ParsableHashFormat parsableHashFormat = (ParsableHashFormat)discoveredFormat;
157 Hash savedHash = parsableHashFormat.parse(saved);
158
159 return passwordsMatch(submittedPlaintext, savedHash);
160 }
161
162
163
164
165
166
167
168
169
170 HashRequest request = createHashRequest(plaintextBytes);
171 Hash computed = this.hashService.computeHash(request);
172 String formatted = this.hashFormat.format(computed);
173
174 return constantEquals(saved, formatted);
175 }
176
177 protected HashRequest buildHashRequest(ByteSource plaintext, Hash saved) {
178
179 return new HashRequest.Builder().setSource(plaintext)
180
181 .setAlgorithmName(saved.getAlgorithmName())
182 .setSalt(saved.getSalt())
183 .setIterations(saved.getIterations())
184 .build();
185 }
186
187 public HashService getHashService() {
188 return hashService;
189 }
190
191 public void setHashService(HashService hashService) {
192 this.hashService = hashService;
193 }
194
195 public HashFormat getHashFormat() {
196 return hashFormat;
197 }
198
199 public void setHashFormat(HashFormat hashFormat) {
200 this.hashFormat = hashFormat;
201 }
202
203 public HashFormatFactory getHashFormatFactory() {
204 return hashFormatFactory;
205 }
206
207 public void setHashFormatFactory(HashFormatFactory hashFormatFactory) {
208 this.hashFormatFactory = hashFormatFactory;
209 }
210 }