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

juanpablo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/jspwiki.git

commit 40f59e63201171623aed9d7d7a28f36e082876c4
Author: Juan Pablo Santos Rodríguez <juanpablo.san...@gmail.com>
AuthorDate: Sun Apr 13 15:33:02 2025 +0200

    Proper digest comparison on CryptoUtil.verifySaltedPassword
---
 .../main/java/org/apache/wiki/util/CryptoUtil.java | 97 ++++++++--------------
 .../java/org/apache/wiki/util/CryptoUtilTest.java  |  3 +-
 2 files changed, 35 insertions(+), 65 deletions(-)

diff --git a/jspwiki-util/src/main/java/org/apache/wiki/util/CryptoUtil.java 
b/jspwiki-util/src/main/java/org/apache/wiki/util/CryptoUtil.java
index b79854cd4..722f23c6a 100644
--- a/jspwiki-util/src/main/java/org/apache/wiki/util/CryptoUtil.java
+++ b/jspwiki-util/src/main/java/org/apache/wiki/util/CryptoUtil.java
@@ -22,7 +22,6 @@ import java.nio.charset.StandardCharsets;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
-import java.util.Arrays;
 import java.util.Base64;
 import java.util.Random;
 
@@ -31,8 +30,8 @@ import java.util.Random;
  * Hashes and verifies salted SHA-1 passwords, which are compliant with RFC
  * 2307.
  */
-public final class CryptoUtil
-{
+public final class CryptoUtil {
+
     private static final String SSHA = "{SSHA}";
 
     private static final String SHA1 = "{SHA-1}";
@@ -52,9 +51,7 @@ public final class CryptoUtil
     /**
      * Private constructor to prevent direct instantiation.
      */
-    private CryptoUtil()
-    {
-    }
+    private CryptoUtil() {}
 
     /**
      * <p>
@@ -80,45 +77,34 @@ public final class CryptoUtil
      * @param args arguments for this method as described above
      * @throws Exception Catches nothing; throws everything up.
      */
-    public static void main( final String[] args ) throws Exception
-    {
+    public static void main( final String[] args ) throws Exception {
         // Print help if the user requested it, or if no arguments
-        if( args.length == 0 || (args.length == 1 && HELP.equals( args[0] )) )
-        {
+        if( args.length == 0 || ( args.length == 1 && HELP.equals( args[0] ) ) 
) {
             System.out.println( "Usage: CryptoUtil [options] " );
             System.out.println( "   --hash   password algorithm             
create hash for password" );
             System.out.println( "   --verify password digest algorithm      
verify password for digest" );
             System.out.println( "Valid algorithm options are {SSHA} and 
{SHA-256}. If no algorithm is specified or an unsupported algorithm is 
specified, SHA-256 is used." );
         }
 
+        if( HASH.equals( args[0] ) ) {
         // User wants to hash the password
-        if( HASH.equals( args[0] ) )
-        {
-            if( args.length < 2 )
-            {
+            if( args.length < 2 ) {
                 throw new IllegalArgumentException( "Error: --hash requires a 
'password' argument." );
             }
             final String password = args[1].trim();
             final String algorithm = args.length > 2 ? args[2].trim() : SHA256;
 
             System.out.println( CryptoUtil.getSaltedPassword( 
password.getBytes( StandardCharsets.UTF_8 ), algorithm ) );
-        }
-
+        } else if( VERIFY.equals( args[0] ) ) {
         // User wants to verify an existing password
-        else if( VERIFY.equals( args[0] ) )
-        {
-            if( args.length < 3 )
-            {
+            if( args.length < 3 ) {
                 throw new IllegalArgumentException( "Error: --hash requires 
'password' and 'digest' arguments." );
             }
             final String password = args[1].trim();
             final String digest = args[2].trim();
 
             System.out.println( CryptoUtil.verifySaltedPassword( 
password.getBytes( StandardCharsets.UTF_8 ), digest ) );
-        }
-
-        else
-        {
+        } else {
             System.out.println( "Wrong usage. Try --help." );
         }
     }
@@ -133,8 +119,7 @@ public final class CryptoUtil
      * </p>
      * <p>
      * In layman's terms, the formula is
-     * <code>digest( secret + salt ) + salt</code>. The resulting digest is
-     * Base64-encoded.
+     * <code>digest( secret + salt ) + salt</code>. The resulting digest is 
Base64-encoded.
      * </p>
      * <p>
      * Note that successive invocations of this method with the same password
@@ -146,9 +131,8 @@ public final class CryptoUtil
      *         <code>{SSHA}</code> or <code>{SHA256}</code>.
      * @throws NoSuchAlgorithmException If your JVM does not supply the 
necessary algorithm. Should not happen.
      */
-    public static String getSaltedPassword(final byte[] password, final String 
algorithm ) throws NoSuchAlgorithmException
-    {
-        final byte[] salt = new byte[DEFAULT_SALT_SIZE];
+    public static String getSaltedPassword( final byte[] password, final 
String algorithm ) throws NoSuchAlgorithmException {
+        final byte[] salt = new byte[ DEFAULT_SALT_SIZE ];
         RANDOM.nextBytes( salt );
 
         return getSaltedPassword( password, salt, algorithm );
@@ -172,8 +156,7 @@ public final class CryptoUtil
      * @return the Base64-encoded password hash, prepended by 
<code>{SSHA}</code> or <code>{SHA256}</code>.
      * @throws NoSuchAlgorithmException If your JVM does not supply the 
necessary algorithm. Should not happen.
      */
-    static String getSaltedPassword(final byte[] password, final byte[] salt, 
final String algorithm ) throws NoSuchAlgorithmException
-    {
+    static String getSaltedPassword( final byte[] password, final byte[] salt, 
final String algorithm ) throws NoSuchAlgorithmException {
         //The term SSHA is used as a password prefix for backwards 
compatibility, but we use SHA-1 when fetching an instance
         //of MessageDigest, as it is the guaranteed option. We also need to 
remove curly braces surrounding the string for
         //backwards compatibility.
@@ -199,72 +182,59 @@ public final class CryptoUtil
      *  @return True, if the password matches.
      *  @throws NoSuchAlgorithmException If there is no SHA available.
      */
-    public static boolean verifySaltedPassword(final byte[] password, final 
String entry ) throws NoSuchAlgorithmException
-    {
-        if( !entry.startsWith( SSHA ) && !entry.startsWith( SHA256 ) )
-        {
+    public static boolean verifySaltedPassword( final byte[] password, final 
String entry ) throws NoSuchAlgorithmException {
+        if( !entry.startsWith( SSHA ) && !entry.startsWith( SHA256 ) ) {
             throw new IllegalArgumentException( "Hash not prefixed by expected 
algorithm; is it really a salted hash?" );
         }
         final String algorithm = entry.startsWith( SSHA ) ? SSHA : SHA256;
-
-        final byte[] challenge = Base64.getDecoder().decode( entry.substring( 
algorithm.length() )
-                .getBytes( StandardCharsets.UTF_8 ) );
+        final byte[] challenge = Base64.getDecoder().decode( entry.substring( 
algorithm.length() ).getBytes( StandardCharsets.UTF_8 ) );
 
         // Extract the password hash and salt
-        final byte[] passwordHash = extractPasswordHash( challenge, 
algorithm.equals(SSHA) ? 20 : 32 );
-        final byte[] salt = extractSalt( challenge, algorithm.equals(SSHA) ? 
20 : 32  );
+        final byte[] passwordHash = extractPasswordHash( challenge, 
algorithm.equals( SSHA ) ? 20 : 32 );
+        final byte[] salt = extractSalt( challenge, algorithm.equals( SSHA ) ? 
20 : 32  );
 
         // Re-create the hash using the password and the extracted salt
         // The term SSHA is used as a password prefix for backwards 
compatibility, but we use SHA-1 when fetching an instance
         // of MessageDigest, as it is the guaranteed option. We also need to 
remove curly braces surrounding the string for
         // backwards compatibility.
-        final String algorithmToUse = algorithm.equals(SSHA) ? SHA1 : 
algorithm;
+        final String algorithmToUse = algorithm.equals( SSHA ) ? SHA1 : 
algorithm;
         final MessageDigest digest = MessageDigest.getInstance( 
algorithmToUse.substring( 1, algorithmToUse.length() -1 ) );
         digest.update( password );
         final byte[] hash = digest.digest( salt );
 
         // See if our extracted hash matches what we just re-created
-        return Arrays.equals( passwordHash, hash );
+        return MessageDigest.isEqual( passwordHash, hash );
     }
 
     /**
      * Helper method that extracts the hashed password fragment from a 
supplied salted SHA-1 or SHA-256 digest
-     * by taking all of the characters before position 20 or 32 depending on 
algorithm.
+     * by taking all the characters before position 20 or 32 depending on 
algorithm.
      * 
-     * @param digest the salted digest, which is assumed to have been
-     *            previously decoded from Base64.
+     * @param digest the salted digest, which is assumed to have been 
previously decoded from Base64.
      * @return the password hash
-     * @throws IllegalArgumentException if the length of the supplied digest is
-     *             less than or equal to 20 bytes
+     * @throws IllegalArgumentException if the length of the supplied digest 
is less than or equal to 20 bytes
      */
-    static byte[] extractPasswordHash(final byte[] digest, final int 
hashLength ) throws IllegalArgumentException
-    {
-        if( digest.length < hashLength )
-        {
+    static byte[] extractPasswordHash( final byte[] digest, final int 
hashLength ) throws IllegalArgumentException {
+        if( digest.length < hashLength ) {
             throw new IllegalArgumentException( "Hash was shorter than 
expected; could not extract password hash!" );
         }
 
         // Extract the password hash
-        final byte[] hash = new byte[hashLength];
-        System.arraycopy(digest, 0, hash, 0, hashLength);
+        final byte[] hash = new byte[ hashLength ];
+        System.arraycopy( digest, 0, hash, 0, hashLength );
 
         return hash;
     }
 
     /**
-     * Helper method that extracts the salt from supplied salted digest by 
taking all of the
-     * characters after a given index.
+     * Helper method that extracts the salt from supplied salted digest by 
taking all the characters after a given index.
      * 
-     * @param digest the salted digest, which is assumed to have been 
previously
-     *            decoded from Base64.
+     * @param digest the salted digest, which is assumed to have been 
previously decoded from Base64.
      * @return the salt
-     * @throws IllegalArgumentException if the length of the supplied digest is
-     *             less than given length.
+     * @throws IllegalArgumentException if the length of the supplied digest 
is less than given length.
      */
-    static byte[] extractSalt(final byte[] digest, final int hashLength ) 
throws IllegalArgumentException
-    {
-        if( digest.length <= hashLength )
-        {
+    static byte[] extractSalt( final byte[] digest, final int hashLength ) 
throws IllegalArgumentException {
+        if( digest.length <= hashLength ) {
             throw new IllegalArgumentException( "Hash was shorter than 
expected; we found no salt!" );
         }
 
@@ -274,4 +244,5 @@ public final class CryptoUtil
 
         return salt;
     }
+
 }
diff --git 
a/jspwiki-util/src/test/java/org/apache/wiki/util/CryptoUtilTest.java 
b/jspwiki-util/src/test/java/org/apache/wiki/util/CryptoUtilTest.java
index 58d7ce70d..b1724adaf 100644
--- a/jspwiki-util/src/test/java/org/apache/wiki/util/CryptoUtilTest.java
+++ b/jspwiki-util/src/test/java/org/apache/wiki/util/CryptoUtilTest.java
@@ -150,8 +150,7 @@ public class CryptoUtilTest {
         Assertions.assertTrue( CryptoUtil.verifySaltedPassword( password, 
"{SSHA}tAVisOOQGAeVyP8UMFQY9qi83lxsb09e" ) );
         Assertions.assertTrue( CryptoUtil.verifySaltedPassword( password, 
"{SSHA}BZaDYvB8czmNW3MjR2j7/mklODV0ZXN0eQ==" ) );
 
-        // Verify with three consecutive random generations (based on
-        // slappasswd)
+        // Verify with three consecutive random generations (based on 
slappasswd)
         password = "testPassword".getBytes();
         Assertions.assertTrue( CryptoUtil.verifySaltedPassword( password, 
"{SSHA}t2tfJHm/QZYUh0OZ8tkm05l2LLbuc3ZF" ) );
         Assertions.assertTrue( CryptoUtil.verifySaltedPassword( password, 
"{SSHA}0FKV9iM2cA5bAMws7mSgwg+zik/GT+wy" ) );

Reply via email to