-
Notifications
You must be signed in to change notification settings - Fork 2.6k
/
AbstractSecureHasher.java
272 lines (243 loc) · 8.86 KB
/
AbstractSecureHasher.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.security.util.crypto;
import org.bouncycastle.util.encoders.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
public abstract class AbstractSecureHasher implements SecureHasher {
private static final Logger logger = LoggerFactory.getLogger(AbstractSecureHasher.class);
protected int saltLength;
private boolean usingStaticSalt;
// A 16 byte salt (nonce) is recommended for password hashing
private static final byte[] STATIC_SALT = "NiFi Static Salt".getBytes(StandardCharsets.UTF_8);
// Upper boundary for several cost parameters
static final Integer UPPER_BOUNDARY = Double.valueOf(Math.pow(2, 32)).intValue() - 1;
/**
* Verifies the salt length is valid for this algorithm and if a static salt should be used.
*
* @param saltLength the salt length in bytes
*/
protected void initializeSalt(Integer saltLength) {
if (saltLength > 0) {
if (!isSaltLengthValid(saltLength)) {
logger.error("The salt length {} is outside the boundary of {} to {}.", saltLength, getMinSaltLength(), getMaxSaltLength());
throw new IllegalArgumentException("Invalid salt length exceeds the saltLength boundary.");
}
this.usingStaticSalt = false;
} else {
this.usingStaticSalt = true;
logger.debug("Configured to use static salt");
}
}
/**
* Returns whether the provided salt length (saltLength) is within boundaries. The lower bound >= (usually) 8 and the
* upper bound <= (usually) {@link Integer#MAX_VALUE}. This method is not {@code static} because it depends on the
* instantiation of the algorithm-specific concrete class.
*
* @param saltLength the salt length in bytes
* @return true if saltLength is within boundaries
*/
public boolean isSaltLengthValid(Integer saltLength) {
final int SALT_LENGTH = getDefaultSaltLength();
if (saltLength == 0) {
logger.debug("The provided salt length 0 indicates a static salt of {} bytes", SALT_LENGTH);
return true;
}
if (saltLength < SALT_LENGTH) {
logger.warn("The provided dynamic salt length {} is below the recommended minimum {}", saltLength, SALT_LENGTH);
}
return saltLength >= getMinSaltLength() && saltLength <= getMaxSaltLength();
}
/**
* Returns the algorithm-specific default salt length in bytes.
*
* @return the default salt length
*/
abstract int getDefaultSaltLength();
/**
* Returns the algorithm-specific minimum salt length in bytes.
*
* @return the min salt length
*/
abstract int getMinSaltLength();
/**
* Returns the algorithm-specific maximum salt length in bytes.
*
* @return the max salt length
*/
abstract int getMaxSaltLength();
/**
* Returns {@code true} if this instance is configured to use a static salt.
*
* @return true if all hashes will be generated using a static salt
*/
public boolean isUsingStaticSalt() {
return usingStaticSalt;
}
/**
* Returns a salt to use. If using a static salt (see {@link #isUsingStaticSalt()}),
* this return value will be identical across every invocation. If using a dynamic salt,
* it will be {@link #saltLength} bytes of a securely-generated random value.
*
* @return the salt value
*/
byte[] getSalt() {
if (isUsingStaticSalt()) {
return STATIC_SALT;
} else {
SecureRandom sr = new SecureRandom();
byte[] salt = new byte[saltLength];
sr.nextBytes(salt);
return salt;
}
}
/**
* Returns the algorithm-specific name for logging and messages.
*
* @return the algorithm name
*/
abstract String getAlgorithmName();
/**
* Returns {@code true} if the algorithm can accept empty (non-{@code null}) inputs.
*
* @return the true if {@code ""} is allowable input
*/
abstract boolean acceptsEmptyInput();
/**
* Returns a String representation of the hash in hex-encoded format.
*
* @param input the non-empty input
* @return the hex-encoded hash
*/
@Override
public String hashHex(String input) {
try {
input = validateInput(input);
} catch (IllegalArgumentException e) {
return "";
}
return Hex.toHexString(hash(input.getBytes(StandardCharsets.UTF_8)));
}
/**
* Returns a String representation of the hash in hex-encoded format.
*
* @param input the non-empty input
* @param salt the provided salt
*
* @return the hex-encoded hash
*/
@Override
public String hashHex(String input, String salt) {
try {
input = validateInput(input);
} catch (IllegalArgumentException e) {
return "";
}
return Hex.toHexString(hash(input.getBytes(StandardCharsets.UTF_8), salt.getBytes(StandardCharsets.UTF_8)));
}
/**
* Returns a String representation of the hash in Base 64-encoded format.
*
* @param input the non-empty input
* @return the Base 64-encoded hash
*/
@Override
public String hashBase64(String input) {
try {
input = validateInput(input);
} catch (IllegalArgumentException e) {
return "";
}
return CipherUtility.encodeBase64NoPadding(hash(input.getBytes(StandardCharsets.UTF_8)));
}
/**
* Returns a String representation of the hash in Base 64-encoded format.
*
* @param input the non-empty input
* @param salt the provided salt
*
* @return the Base 64-encoded hash
*/
@Override
public String hashBase64(String input, String salt) {
try {
input = validateInput(input);
} catch (IllegalArgumentException e) {
return "";
}
return CipherUtility.encodeBase64NoPadding(hash(input.getBytes(StandardCharsets.UTF_8), salt.getBytes(StandardCharsets.UTF_8)));
}
/**
* Returns a byte[] representation of {@code SecureHasher.hash(input)}.
*
* @param input the input
* @return the hash
*/
public byte[] hashRaw(byte[] input) {
return hash(input);
}
/**
* Returns a byte[] representation of {@code SecureHasher.hash(input)}.
*
* @param input the input
* @param salt the provided salt
*
* @return the hash
*/
@Override
public byte[] hashRaw(byte[] input, byte[] salt) {
return hash(input, salt);
}
/**
* Returns the valid {@code input} String (if the algorithm accepts empty input, changes {@code null} to {@code ""}; if not, throws {@link IllegalArgumentException}).
*
* @param input the input to validate
* @return a valid input string
*/
private String validateInput(String input) {
if (acceptsEmptyInput()) {
if (input == null) {
logger.warn("Attempting to generate a hash using {} of null input; using empty input", getAlgorithmName());
return "";
}
} else {
if (input == null || input.length() == 0) {
logger.warn("Attempting to generate a hash using {} of null or empty input; returning 0 length string", getAlgorithmName());
throw new IllegalArgumentException();
}
}
return input;
}
/**
* Returns the algorithm-specific calculated hash for the input and generates or retrieves the salt according to
* the configured salt length.
*
* @param input the input in raw bytes
* @return the hash in raw bytes
*/
abstract byte[] hash(byte[] input);
/**
* Returns the algorithm-specific calculated hash for the input and salt.
*
* @param input the input in raw bytes
* @param salt the provided salt
* @return the hash in raw bytes
*/
abstract byte[] hash(byte[] input, byte[] salt);
}