Skip to content

Commit f7ac215

Browse files
committed
Close GitHub issue #245.
Add and clean-up some comments. Allow 'purpose' for argument in KeyDerivationFunction.computeDerivedKey() to be generalized. (I.e., removed checks to restrict it to "encryption" and "authenticity".) That allows this method to be used in a more general KDF context.
1 parent 1ca26f5 commit f7ac215

File tree

1 file changed

+44
-11
lines changed

1 file changed

+44
-11
lines changed

src/main/java/org/owasp/esapi/crypto/KeyDerivationFunction.java

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ public int getVersion() {
187187
}
188188

189189

190-
// TODO: IMPORTANT NOTE: In a future release (hopefully starting in 2.1.1),
190+
// TODO: IMPORTANT NOTE: In a future release (hopefully starting in 2.3),
191191
// we will be using the 'context' to mix in some additional things. At a
192192
// minimum, we will be using the KDF version (version_) so that downgrade version
193193
// attacks are not possible. Other candidates are the cipher xform and
@@ -267,24 +267,26 @@ public String getContext() {
267267
* @param keySize The cipher's key size (in bits) for the {@code keyDerivationKey}.
268268
* Must have a minimum size of 56 bits and be an integral multiple of 8-bits.
269269
* <b>Note:</b> The derived key will have the same size as this.
270-
* @param purpose The purpose for the derived key. For the ESAPI reference implementation,
270+
* @param purpose The purpose for the derived key. <b>IMPORTANT:</b> For the ESAPI reference implementation,
271271
* {@code JavaEncryptor}, this <i>must</i> be either the string "encryption" or
272272
* "authenticity", where "encryption" is used for creating a derived key to use
273273
* for confidentiality, and "authenticity" is used for creating a derived key to
274274
* use with a MAC to ensure message authenticity. However, since parameter serves
275275
* the same purpose as the "Label" in section 5.1 of NIST SP 800-108, it really can
276276
* be set to anything other than {@code null} or an empty string when called outside
277-
* of {@code JavaEncryptor}.
277+
* of ESAPI's {@code JavaEncryptor} reference implementation (but you must consistent).
278278
* @return The derived {@code SecretKey} to be used according
279279
* to the specified purpose.
280280
* @throws NoSuchAlgorithmException The {@code keyDerivationKey} has an unsupported
281-
* encryption algorithm or no current JCE provider supports
282-
* "HmacSHA1".
281+
* encryption algorithm or no current JCE provider supports requested
282+
* Hmac algorithrm used for the PRF for key generation.
283283
* @throws EncryptionException If "UTF-8" is not supported as an encoding, then
284284
* this is thrown with the original {@code UnsupportedEncodingException}
285285
* as the cause. (NOTE: This should never happen as "UTF-8" is supposed to
286286
* be a common encoding supported by all Java implementations. Support
287-
* for it is usually in rt.jar.)
287+
* for it is usually in rt.jar.) This exception is also thrown if the
288+
* requested {@code keySize} parameter exceeds the length of the number of
289+
* bytes provded in the {@code keyDerivationKey} parameter.
288290
* @throws InvalidKeyException Likely indicates a coding error. Should not happen.
289291
* @throws EncryptionException Throw for some precondition violations.
290292
*/
@@ -322,7 +324,29 @@ public SecretKey computeDerivedKey(SecretKey keyDerivationKey, int keySize, Stri
322324
throw new IllegalArgumentException("Purpose may not be null or empty.");
323325
}
324326

325-
keySize = calcKeySize( keySize ); // Safely convert to whole # of bytes.
327+
//
328+
// No longer, since we no longer wish to restrict this to use only by JavaEncryptor, so
329+
// we no longer test for this. For details, see the javadoc for '@param purpose', above.
330+
//
331+
/*
332+
*
333+
* if ( ! ( purpose.equals("encryption") || purpose.equals("authenticity") ) ) {
334+
* throw new IllegalArgumentException("Purpose must be \"encryption\" or \"authenticity\".");
335+
* }
336+
*
337+
*/
338+
339+
int providedKeyLen = 8 * keyDerivationKey.getEncoded().length;
340+
// assert providedKeyLen >= 56 : "Coding error? Length of keyDerivationKey < 56 bits!"; // Ugh. DES compatible.
341+
342+
if ( providedKeyLen < keySize ) {
343+
throw new EncryptionException("KeyDerivationFunction.computeDerivedKey() not intended for key stretching: " +
344+
"provided key too short (" + providedKeyLen + " bits) to provide " + keySize + " bits.",
345+
"Key stretching not supported: Provided key, keyDerivationKey, has insufficient entropy (" +
346+
providedKeyLen + " bits) to generate key of requested size of " + keySize + " bits.");
347+
}
348+
349+
keySize = calcKeySize( keySize ); // Safely convert from bits to a whole # of bytes.
326350
byte[] derivedKey = new byte[ keySize ];
327351
byte[] label; // Same purpose as NIST SP 800-108's "label" in section 5.1.
328352
byte[] context; // See setContext() for details.
@@ -344,20 +368,20 @@ public SecretKey computeDerivedKey(SecretKey keyDerivationKey, int keySize, Stri
344368
// under the hood to create 'sk' so that it is 20 bytes. I cannot vouch
345369
// for how secure this key-stretching is. Worse, it might not be specified
346370
// as to *how* it is done and left to each JCE provider.
347-
SecretKey sk = new SecretKeySpec(keyDerivationKey.getEncoded(), "HmacSHA1");
371+
SecretKey sk = new SecretKeySpec(keyDerivationKey.getEncoded(), prfAlg_ );
348372
Mac mac = null;
349373

350374
try {
351-
mac = Mac.getInstance("HmacSHA1");
375+
mac = Mac.getInstance( prfAlg_ );
352376
mac.init(sk);
353377
} catch( InvalidKeyException ex ) {
354378
logger.error(Logger.SECURITY_FAILURE,
355-
"Created HmacSHA1 Mac but SecretKey sk has alg " +
379+
"Created " + prfAlg_ + " Mac but SecretKey sk has alg " +
356380
sk.getAlgorithm(), ex);
357381
throw ex;
358382
}
359383

360-
// Repeatedly call of HmacSHA1 hash until we've collected enough bits
384+
// Repeatedly call of PRF Hmac until we've collected enough bits
361385
// for the derived key. The first time through, we calculate the HmacSHA1
362386
// on the "purpose" string, but subsequent calculations are performed
363387
// on the previous result.
@@ -415,6 +439,15 @@ public SecretKey computeDerivedKey(SecretKey keyDerivationKey, int keySize, Stri
415439

416440
// Don't leave remnants of the partial key in memory. (Note: we could
417441
// not do this if tmpKey were declared in the do-while loop.
442+
// Of course, in reality, trying to stomp these bits out is probably not
443+
// realistic because the JIT is likely toing to be smart enough to
444+
// optimze this loop away. We probably could try to outsmart it, by
445+
// (say) writing out the overwritten bits to /dev/null, but then even
446+
// then we'd still probably have to overwrite with random bits rather
447+
// than all null chars. How much is enough? Who knows? But it does point
448+
// to a serious limitation in Java and many other languages that one
449+
// cannot arbitrarily disable the optimizer either at compile time or
450+
// run time because of security reasons. Sigh. At least we've tried.
418451
for ( int i = 0; i < tmpKey.length; i++ ) {
419452
tmpKey[i] = '\0';
420453
}

0 commit comments

Comments
 (0)