This is an automated email from the ASF dual-hosted git repository.

garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-codec.git


The following commit(s) were added to refs/heads/master by this push:
     new 4611565f Allocate a single MessageDigest and use it in 
Sha2Crypt.sha2Crypt(byte[], String, String, int, String).
4611565f is described below

commit 4611565fe5cc748520025f015733a32867e3c645
Author: Gary Gregory <[email protected]>
AuthorDate: Thu Aug 6 12:49:01 2026 -0400

    Allocate a single MessageDigest and use it in
    Sha2Crypt.sha2Crypt(byte[], String, String, int, String).
---
 src/changes/changes.xml                            |  1 +
 .../org/apache/commons/codec/digest/Sha2Crypt.java | 44 +++++++++++-----------
 .../apache/commons/codec/digest/Sha2CryptTest.java | 14 +++++++
 3 files changed, 37 insertions(+), 22 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 7abfb525..bf6ce8d8 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -48,6 +48,7 @@ The <action> type attribute can be add,update,fix,remove.
       <action type="fix" dev="ggregory" due-to="Yu Bao, Gary Gregory">Optimize 
PhoneticEngine.encode(String, LanguageSet) for speed.</action>
       <action type="fix" dev="ggregory" due-to="Yu Bao, Gary 
Gregory">RFC1522Codec.decodeText(String) now throws a DecoderException instead 
of a StringIndexOutOfBoundsException when a separator is missing.</action>
       <action type="fix" dev="ggregory" due-to="Yu Bao, Gary Gregory">Optimize 
Base58.convertFromBase58(byte[], Context) for speed and temp object 
allocation.</action>
+      <action type="fix" dev="ggregory" due-to="Yu Bao, Gary Gregory">Allocate 
a single MessageDigest and use it in Sha2Crypt.sha2Crypt(byte[], String, 
String, int, String)..</action>
       <!-- ADD -->
       <action type="add" dev="ggregory" due-to="Gary Gregory">Add and use 
PhoneticEngine.Builder and deprecate old constructors.</action>
       <!-- UPDATE -->
diff --git a/src/main/java/org/apache/commons/codec/digest/Sha2Crypt.java 
b/src/main/java/org/apache/commons/codec/digest/Sha2Crypt.java
index 101d8ae2..9c9212d4 100644
--- a/src/main/java/org/apache/commons/codec/digest/Sha2Crypt.java
+++ b/src/main/java/org/apache/commons/codec/digest/Sha2Crypt.java
@@ -177,7 +177,7 @@ public class Sha2Crypt {
 
         // 1. start digest A
         // Prepare for the real work.
-        MessageDigest messageDigest = DigestUtils.getDigest(algorithm);
+        final MessageDigest messageDigest = DigestUtils.getDigest(algorithm);
 
         // 2. the password string is added to digest A
         /*
@@ -202,34 +202,34 @@ public class Sha2Crypt {
 
         // 4. start digest B
         /*
-         * Compute alternate sha512 sum with input KEY, SALT, and KEY. The 
final result will be added to the first
+         * Compute alternate SHA sum with input KEY, SALT, and KEY. The final 
result will be added to the first
          * context.
          */
-        MessageDigest altMessageDigestMd5 = DigestUtils.getDigest(algorithm);
+        final MessageDigest altDigest = DigestUtils.getDigest(algorithm);
 
         // 5. add the password to digest B
         /*
          * Add key.
          */
-        altMessageDigestMd5.update(keyBytes);
+        altDigest.update(keyBytes);
 
         // 6. add the salt string to digest B
         /*
          * Add salt.
          */
-        altMessageDigestMd5.update(saltBytes);
+        altDigest.update(saltBytes);
 
         // 7. add the password again to digest B
         /*
          * Add key again.
          */
-        altMessageDigestMd5.update(keyBytes);
+        altDigest.update(keyBytes);
 
         // 8. finish digest B
         /*
          * Now get result of this (32 bytes) and add it to the other context.
          */
-        byte[] altResult = altMessageDigestMd5.digest();
+        byte[] altResult = altDigest.digest();
 
         // 9. For each block of 32 or 64 bytes in the password string 
(excluding
         // the terminating NUL in the C representation), add digest B to 
digest A
@@ -239,7 +239,7 @@ public class Sha2Crypt {
         /*
          * (Remark: the C code comment seems wrong for key length > 32!)
          */
-        int cnt = keyBytes.length;
+        int cnt = keyLen;
         while (cnt > blocksize) {
             messageDigest.update(altResult, 0, blocksize);
             cnt -= blocksize;
@@ -263,7 +263,7 @@ public class Sha2Crypt {
          * Take the binary representation of the length of the key and for 
every 1 add the alternate sum, for every 0
          * the key.
          */
-        cnt = keyBytes.length;
+        cnt = keyLen;
         while (cnt > 0) {
             if ((cnt & 1) != 0) {
                 messageDigest.update(altResult, 0, blocksize);
@@ -283,7 +283,7 @@ public class Sha2Crypt {
         /*
          * Start computation of P byte sequence.
          */
-        altMessageDigestMd5 = DigestUtils.getDigest(algorithm);
+        altDigest.reset();
 
         // 14. for every byte in the password (excluding the terminating NUL 
byte
         // in the C representation of the string)
@@ -293,14 +293,14 @@ public class Sha2Crypt {
          * For every character in the password add the entire password.
          */
         for (int i = 1; i <= keyLen; i++) {
-            altMessageDigestMd5.update(keyBytes);
+            altDigest.update(keyBytes);
         }
 
         // 15. finish digest DP
         /*
          * Finish the digest.
          */
-        byte[] tempResult = altMessageDigestMd5.digest();
+        byte[] tempResult = altDigest.digest();
 
         // 16. produce byte sequence P of the same length as the password where
         //
@@ -324,7 +324,7 @@ public class Sha2Crypt {
         /*
          * Start computation of S byte sequence.
          */
-        altMessageDigestMd5 = DigestUtils.getDigest(algorithm);
+        altDigest.reset();
 
         // 18. repeat the following 16+A[0] times, where A[0] represents the 
first
         // byte in digest A interpreted as an 8-bit unsigned value
@@ -334,14 +334,14 @@ public class Sha2Crypt {
          * For every character in the password add the entire password.
          */
         for (int i = 1; i <= 16 + (altResult[0] & 0xff); i++) {
-            altMessageDigestMd5.update(saltBytes);
+            altDigest.update(saltBytes);
         }
 
         // 19. finish digest DS
         /*
          * Finish the digest.
          */
-        tempResult = altMessageDigestMd5.digest();
+        tempResult = altDigest.digest();
 
         // 20. produce byte sequence S of the same length as the salt string 
where
         //
@@ -370,15 +370,14 @@ public class Sha2Crypt {
         // digest produced in step 12. In the latter steps it is the digest
         // produced in step 21.h. The following text uses the notation
         // "digest A/C" to describe this behavior.
-        /*
-         * Repeatedly run the collected hash value through sha512 to burn CPU 
cycles.
-         */
-        for (int i = 0; i <= rounds - 1; i++) {
+        //
+        // Repeatedly run the collected hash value through SHA to burn CPU 
cycles.
+        for (int i = 0; i < rounds; i++) {
             // a) start digest C
             /*
-             * New context.
+             * Reset and reuse the existing digest context.
              */
-            messageDigest = DigestUtils.getDigest(algorithm);
+            messageDigest.reset();
 
             // b) for odd round numbers add the byte sequence P to digest C
             // c) for even round numbers add digest A/C
@@ -515,11 +514,12 @@ public class Sha2Crypt {
          * cannot get any information.
          */
         // Is there a better way to do this with the JVM?
+        Arrays.fill(altResult, (byte) 0);
         Arrays.fill(tempResult, (byte) 0);
         Arrays.fill(bytes, (byte) 0);
         Arrays.fill(sBytes, (byte) 0);
         messageDigest.reset();
-        altMessageDigestMd5.reset();
+        altDigest.reset();
         Arrays.fill(keyBytes, (byte) 0);
         Arrays.fill(saltBytes, (byte) 0);
 
diff --git a/src/test/java/org/apache/commons/codec/digest/Sha2CryptTest.java 
b/src/test/java/org/apache/commons/codec/digest/Sha2CryptTest.java
index c148888f..097ef9a0 100644
--- a/src/test/java/org/apache/commons/codec/digest/Sha2CryptTest.java
+++ b/src/test/java/org/apache/commons/codec/digest/Sha2CryptTest.java
@@ -19,7 +19,11 @@ package org.apache.commons.codec.digest;
 
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 
+import java.nio.charset.StandardCharsets;
+
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 
 class Sha2CryptTest {
 
@@ -28,4 +32,14 @@ class Sha2CryptTest {
         assertNotNull(new Sha2Crypt());
     }
 
+    @ParameterizedTest
+    @ValueSource(ints = { 100_000, 1_000_000, 5_000_000 /*, 50_000_000*/ })
+    void testLargeRounds(final int rounds) {
+        final String salt = "$6$rounds=" + rounds + "$abcdefghijklmnop";
+        final long t = System.nanoTime();
+        Crypt.crypt("anything".getBytes(StandardCharsets.UTF_8), salt);
+        Crypt.crypt("anything".getBytes(StandardCharsets.UTF_8), 
"$6$rounds=5000000$abcdefghijklmnop");
+        // Full effect (WARNING: ~2 min):
+        // Crypt.crypt("anything".getBytes(), 
"$6$rounds=999999999$abcdefghijklmnop");
+    }
 }

Reply via email to