Skip to content

Commit cd65993

Browse files
authored
Merge pull request #506 from kwwall/issue-245
Not going to pretend I'm smart enough to understand the changes made in #245. Crypto isn't my top skill. Overall all these changes appear to be pretty small overall. Merging.
2 parents f9564e9 + 4ccc558 commit cd65993

File tree

8 files changed

+286
-24
lines changed

8 files changed

+286
-24
lines changed
-71.9 KB
Binary file not shown.
0 Bytes
Binary file not shown.

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

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -697,9 +697,12 @@ public String toString() {
697697
int n = getRawCipherTextByteLength();
698698
String rawCipherText = (( n > 0 ) ? "present (" + n + " bytes)" : "absent");
699699
String mac = (( separate_mac_ != null ) ? "present" : "absent");
700-
sb.append("Creation time: ").append(creationTime);
701-
sb.append(", raw ciphertext is ").append(rawCipherText);
702-
sb.append(", MAC is ").append(mac).append("; ");
700+
701+
sb.append("KDF Version: ").append( kdfVersion_ );
702+
sb.append(", KDF PRF: ").append( kdfPRFAsInt() );
703+
sb.append("; Creation time: ").append(creationTime);
704+
sb.append("; raw ciphertext is ").append(rawCipherText);
705+
sb.append("; MAC is ").append(mac).append("; ");
703706
sb.append( cipherSpec_.toString() );
704707
return sb.toString();
705708
}
@@ -792,6 +795,15 @@ protected boolean canEqual(Object other) {
792795
* <pre>
793796
* HMAC-SHA1(nonce, IV + plaintext)
794797
* </pre>
798+
* Note that <i>only</i> HMAC-SHA1 is used for the MAC calcuation. Unlike
799+
* the PRF used for derived key generation in the {@code KeyDerivationFunction}
800+
* class, the user cannot change the algorithm used to compute the MAC itself.
801+
* One reason for that is that we don't want the MAC value to be excessively
802+
* long; 128 bits is already quite long when only encrypting short strings.
803+
* Also while the NSA reviewed this and were okay with it, Bellare, Canetti & Krawczyk
804+
* proved in 1996 [see http://pssic.free.fr/Extra%20Reading/SEC+/SEC+/hmac-cb.pdf] that
805+
* HMAC security doesn’t require that the underlying hash function be collision resistant,
806+
* but only that it acts as a pseudo-random function, which SHA1 satisfies.
795807
* @param ciphertext The ciphertext value for which the MAC is computed.
796808
* @return The value for the MAC.
797809
*/

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

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -126,18 +126,23 @@ public static SecretKey generateSecretKey(String alg, int keySize)
126126
* this is thrown with the original {@code UnsupportedEncodingException}
127127
* as the cause. (NOTE: This should never happen as "UTF-8" is supposed to
128128
* be a common encoding supported by all Java implementations. Support
129-
* for it is usually in rt.jar.)
129+
* for it is usually in rt.jar.) This exception is also thrown if the
130+
* requested {@code keySize} parameter exceeds the length of the number of
131+
* bytes provded in the {@code keyDerivationKey} parameter.
130132
* @throws InvalidKeyException Likely indicates a coding error. Should not happen.
131133
* @throws EncryptionException Throw for some precondition violations.
132-
* @deprecated Use{@code KeyDerivationFunction} instead. This method will be removed as of
133-
* ESAPI release 2.3 so if you are using this, please change your code.
134+
* @deprecated Use same method in {@code KeyDerivationFunction} instead. This method will be <b>removed</b> as of
135+
* ESAPI release 2.3 so if you are using this, please CHANGE YOUR CODE. Note that the replacement
136+
* is not a static method, so create your own wrapper if you wish, but this will soon disappear.
134137
*/
135138
@Deprecated
136139
public static SecretKey computeDerivedKey(SecretKey keyDerivationKey, int keySize, String purpose)
137140
throws NoSuchAlgorithmException, InvalidKeyException, EncryptionException
138141
{
139-
// These really should be turned into actual runtime checks and an
140-
// IllegalArgumentException should be thrown if they are violated.
142+
// Fingers cross; maybe this will help.
143+
logger.warning(Logger.SECURITY_AUDIT,
144+
"Your code is using the deprecated CryptoHelper.computeDerivedKey() method which will be removed next release");
145+
141146
if ( keyDerivationKey == null ) {
142147
throw new IllegalArgumentException("Key derivation key cannot be null.");
143148
}
@@ -159,6 +164,9 @@ public static SecretKey computeDerivedKey(SecretKey keyDerivationKey, int keySiz
159164
// DISCUSS: Should we use HmacSHA1 (what we were using) or the HMAC defined by
160165
// Encryptor.KDF.PRF instead? Either way, this is not compatible with
161166
// previous ESAPI versions. JavaEncryptor doesn't use this any longer.
167+
// ANSWER: This is deprecated and will be removed in 2.3.0.0, so it really matter
168+
// that much. However, Since the property Encryptor.KDF.PRF is (and has
169+
// been) "HMacSHA256". changing this could unintentionally break code.
162170
KeyDerivationFunction kdf = new KeyDerivationFunction(
163171
KeyDerivationFunction.PRF_ALGORITHMS.HmacSHA1);
164172
return kdf.computeDerivedKey(keyDerivationKey, keySize, purpose);
@@ -260,7 +268,8 @@ public static boolean isCipherTextMACvalid(SecretKey sk, CipherText ct)
260268
{
261269
if ( CryptoHelper.isMACRequired( ct ) ) {
262270
try {
263-
SecretKey authKey = CryptoHelper.computeDerivedKey( sk, ct.getKeySize(), "authenticity");
271+
KeyDerivationFunction kdf = new KeyDerivationFunction( ct.getKDF_PRF() );
272+
SecretKey authKey = kdf.computeDerivedKey(sk, ct.getKeySize(), "authenticity");
264273
boolean validMAC = ct.validateMAC( authKey );
265274
return validMAC;
266275
} catch (Exception ex) {

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
}

src/test/java/org/owasp/esapi/crypto/CipherTextTest.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,8 @@ public final void testMIC() {
191191
encryptor.init(Cipher.ENCRYPT_MODE, key, ivSpec);
192192
byte[] raw = encryptor.doFinal("This is my secret message!!!".getBytes("UTF8"));
193193
CipherText ciphertext = new CipherText(cipherSpec, raw);
194-
// TODO: Replace this w/ call to KeyDerivationFunction as this is
195-
// deprecated! Shame on me!
196-
SecretKey authKey = CryptoHelper.computeDerivedKey(key, key.getEncoded().length * 8, "authenticity");
194+
KeyDerivationFunction kdf = new KeyDerivationFunction( KeyDerivationFunction.PRF_ALGORITHMS.HmacSHA1 );
195+
SecretKey authKey = kdf.computeDerivedKey(key, key.getEncoded().length * 8, "authenticity");
197196
ciphertext.computeAndStoreMAC( authKey );
198197
// System.err.println("Original ciphertext being serialized: " + ciphertext);
199198
byte[] serializedBytes = ciphertext.asPortableSerializedByteArray();

0 commit comments

Comments
 (0)