1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.shiro.crypto.hash;
21
22 import org.apache.shiro.crypto.hash.format.Shiro1CryptFormat;
23 import org.apache.shiro.lang.util.ByteSource;
24 import org.apache.shiro.lang.util.SimpleByteSource;
25
26 import java.util.Arrays;
27 import java.util.Base64;
28 import java.util.NoSuchElementException;
29 import java.util.Optional;
30 import java.util.Random;
31 import java.util.Set;
32
33 import static java.util.Collections.unmodifiableSet;
34 import static java.util.stream.Collectors.toSet;
35
36
37
38
39
40
41
42 public class SimpleHashProvider implements HashSpi {
43
44 private static final Set<String> IMPLEMENTED_ALGORITHMS = Arrays.stream(new String[] {
45 Sha256Hash.ALGORITHM_NAME,
46 Sha384Hash.ALGORITHM_NAME,
47 Sha512Hash.ALGORITHM_NAME
48 })
49 .collect(toSet());
50
51 @Override
52 public Set<String> getImplementedAlgorithms() {
53 return unmodifiableSet(IMPLEMENTED_ALGORITHMS);
54 }
55
56 @Override
57 public SimpleHash fromString(String format) {
58 Hash hash = new Shiro1CryptFormat().parse(format);
59
60 if (!(hash instanceof SimpleHash)) {
61 throw new IllegalArgumentException("formatted string was not a simple hash: " + format);
62 }
63
64 return (SimpleHash) hash;
65 }
66
67 @Override
68 public HashFactory newHashFactory(Random random) {
69 return new SimpleHashFactory(random);
70 }
71
72 static class SimpleHashFactory implements HashSpi.HashFactory {
73
74 private final Random random;
75
76 SimpleHashFactory(Random random) {
77 this.random = random;
78 }
79
80 @Override
81 public SimpleHash generate(HashRequest hashRequest) {
82 String algorithmName = hashRequest.getAlgorithmName().orElse(Parameters.DEFAULT_ALGORITHM);
83 ByteSource source = hashRequest.getSource();
84 final int iterations = getIterations(hashRequest);
85
86 final ByteSource publicSalt = getPublicSalt(hashRequest);
87 final ByteSource secretSalt = getSecretSalt(hashRequest);
88 final ByteSource salt = combine(secretSalt, publicSalt);
89
90 return createSimpleHash(algorithmName, source, iterations, publicSalt, salt);
91 }
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108 protected ByteSource getPublicSalt(HashRequest request) {
109 Optional<ByteSource> publicSalt = request.getSalt();
110
111 if (publicSalt.isPresent() && !publicSalt.orElseThrow(NoSuchElementException::new).isEmpty()) {
112
113 return publicSalt.orElseThrow(NoSuchElementException::new);
114 }
115
116
117 @SuppressWarnings("checkstyle:MagicNumber")
118 byte[] ps = new byte[16];
119 random.nextBytes(ps);
120
121 return new SimpleByteSource(ps);
122 }
123
124 private ByteSource getSecretSalt(HashRequest request) {
125 Optional<Object> secretSalt = Optional.ofNullable(request.getParameters().get(Parameters.PARAMETER_SECRET_SALT));
126
127 return secretSalt
128 .map(salt -> (String) salt)
129 .map(salt -> Base64.getDecoder().decode(salt))
130 .map(SimpleByteSource::new)
131 .orElse(null);
132 }
133
134 private SimpleHash createSimpleHash(String algorithmName, ByteSource source,
135 int iterations, ByteSource publicSalt, ByteSource salt) {
136 Hash computed = new SimpleHash(algorithmName, source, salt, iterations);
137
138 SimpleHash result = new SimpleHash(algorithmName);
139 result.setBytes(computed.getBytes());
140 result.setIterations(iterations);
141
142 result.setSalt(publicSalt);
143
144 return result;
145 }
146
147 protected int getIterations(HashRequest request) {
148 Object parameterIterations = request.getParameters().getOrDefault(Parameters.PARAMETER_ITERATIONS, 0);
149
150 if (!(parameterIterations instanceof Integer)) {
151 return Parameters.DEFAULT_ITERATIONS;
152 }
153
154 final int iterations = Math.max(0, (Integer) parameterIterations);
155
156 if (iterations < 1) {
157 return Parameters.DEFAULT_ITERATIONS;
158 }
159
160 return iterations;
161 }
162
163
164
165
166
167
168
169
170
171
172
173 protected ByteSource combine(ByteSource privateSalt, ByteSource publicSalt) {
174
175
176 byte[] privateSaltBytes = privateSalt != null ? privateSalt.getBytes() : null;
177 int privateSaltLength = privateSaltBytes != null ? privateSaltBytes.length : 0;
178
179
180 byte[] publicSaltBytes = publicSalt.getBytes();
181 int extraBytesLength = publicSaltBytes.length;
182
183 int length = privateSaltLength + extraBytesLength;
184
185 if (length <= 0) {
186 return SimpleByteSource.empty();
187 }
188
189 byte[] combined = new byte[length];
190
191 int i = 0;
192 for (int j = 0; j < privateSaltLength; j++) {
193 combined[i++] = privateSaltBytes[j];
194 }
195 for (int j = 0; j < extraBytesLength; j++) {
196 combined[i++] = publicSaltBytes[j];
197 }
198
199 return ByteSource.Util.bytes(combined);
200 }
201 }
202
203 static final class Parameters {
204 public static final String PARAMETER_ITERATIONS = "SimpleHash.iterations";
205
206
207
208
209
210
211
212 public static final String PARAMETER_SECRET_SALT = "SimpleHash.secretSalt";
213
214 public static final String DEFAULT_ALGORITHM = "SHA-512";
215
216 public static final int DEFAULT_ITERATIONS = 50_000;
217
218
219 private Parameters() {
220
221 }
222 }
223 }