Custom Password Plugin fails to generate correct hash



  • Description
    I have deployed a fusion auth instance on AWS ECS. Fusion auth version 1.17.5. I create a python script that imported all my users from the current database to fusion auth using python client. I imported also the password hash and the salt.
    I created a plugin in java and installed it on fusion auth.

    public class GyoPasswordEncryptor implements PasswordEncryptor {
      @Override
      public int defaultFactor() {
        return 1;
      }
    
      @Override
      public String encrypt(String password, String salt, int factor) {
    
        System.out.println("1. Enter Encrypt Method");
    
        System.out.printf("Password: %s  Salt: %s", password,salt);
    
        MessageDigest digest1;
        MessageDigest digest2;
        try {
          digest1 = MessageDigest.getInstance("SHA-256");
          digest2 = MessageDigest.getInstance("SHA-256");
        } catch (NoSuchAlgorithmException ex) {
          return null;
        }
    
        byte[] passwordBytes = password.getBytes(StandardCharsets.UTF_8);
    
        digest1.update(passwordBytes);
    
        byte[] passwordHash = digest1.digest();
    
        StringBuilder sb = new StringBuilder();
        for(int i=0; i< passwordHash.length ;i++)
        {
          sb.append(Integer.toString((passwordHash[i] & 0xff) + 0x100, 16).substring(1));
        }
    
        String saltAndPasswordHash = salt+sb.toString();
        digest2.update(saltAndPasswordHash.getBytes(StandardCharsets.UTF_8));
    
        byte[] hashedPassword = digest2.digest();
    
        sb = new StringBuilder();
        for(int i=0; i< hashedPassword.length ;i++)
        {
          sb.append(Integer.toString((hashedPassword[i] & 0xff) + 0x100, 16).substring(1));
        }
    
        String finalHash = sb.toString();
    
        System.out.printf("Password Hash: %s",finalHash);
    
        return finalHash;
    
      }
    }
    

    The password for one of the test users is: HtZX28CECnaL and the salt is: 18f. The generated hash from the script is df6cb6dad23e6f5d98e3edfe7f3492a137f53041093ca6adb39bca588abfb6dd and the password hash imported from the previous database is the same df6cb6dad23e6f5d98e3edfe7f3492a137f53041093ca6adb39bca588abfb6dd.

    When I try to login with this user the logs that are printed from the password plugin are:

    1. Enter Encrypt Method
    Password: enumerate  Salt: dGhpcw==
    Password Hash: adbc8778a3de37e8912616006d9eb2ab2507fc3d1d1b189bf2b635e11a4690a2
    

    If I change the password from HtZX28CECnaL to Admin@123+ using the admin panel and try to login I get the following logs

    1. Enter Encrypt Method
    Password: Admin@123+  Salt: kud+SSpxm1PQHNxzNihLcJ8EiQPiwe0mSGiJpUKjTT0=
    Password Hash: 1185fe9808d625dac019eb4c5662d83d0fb39d129349a3a30906c99dd5914983
    

    But even in the second time that the password is passed successfully on the encrypt method and the hash generated is the same as the database the fusion auth fails to perform the login, it is still stuck in the login page, it doesn't redirect me to the redirect url.



  • Are you specifying the custom encryption scheme for your user? This is mentioned here: https://fusionauth.io/docs/v1/tech/plugins/password-encryptors and https://fusionauth.io/docs/v1/tech/apis/users (search for encryptionScheme).



  • @dan Yes I am specifying the encryption schema during the user import. I am using python library to perform the import using import_users. This is how an object looks like on the import script.

                        template_request['users'].append({
                            'sendSetPasswordEmail': False,
                            'skipVerification': True,
                            "active": True,
                            "birthDate": user.birthdate,
                            "data": {
                                "displayName": user.first_name + " " + user.last_name
                            },
                            "email": user.email,
                            "encryptionScheme": "gyo-password-encryptor",
                            "expiry": 1571786483322,
                            "factor": 24000,
                            "firstName": user.first_name,
                            "fullName": user.first_name + " " + user.middle_name + " " + user.last_name,
                            "imageUrl": "http://65.media.tumblr.com/tumblr_l7dbl0MHbU1qz50x3o1_500.png",
                            "insertInstant": 1331449200000,
                            "lastName": user.last_name,
                            "middleName": user.middle_name,
                            "password": user.password,
                            "passwordChangeRequired": False,
                            "preferredLanguages": [
                                "en"
                            ],
                            "salt": user.salt,
                            "timezone": "America/Denver",
                            "twoFactorEnabled": False,
                            "usernameStatus": "ACTIVE",
                            "username": user.username,
                            "verified": True
                        })
    

    The user object is the data that I am getting from the source database that I am trying to import on fusion auth. I am sure that the password plugin is specified correctly since I can see the custom logs that I have added on password plugin.



  • @kejvidoko said in Custom Password Plugin fails to generate correct hash:

    But even in the second time that the password is passed successfully on the encrypt method and the hash generated is the same as the database the fusion auth fails to perform the login, it is still stuck in the login page, it doesn't redirect me to the redirect url.

    Are you creating a registration for the user for the application in FusionAuth? You need to both create a user and create a registration for a user to associate them with a given application before they can login.

    I'm not sure what's going on with the password hashing. Can you push all your code up to a github someplace so that I can take a look?



  • @dan How can I generate a registration for the user. I am using this API https://fusionauth.io/docs/v1/tech/apis/users#import-users here in the documentation doesn't mention anything about registration. Do I have to call this method too fa_client.generate_registration_verification_id('email','application_id') ?



  • Hiya,

    I think the best thing would be to use the registrations key in the user object you are generating. That will associate the user with the application.

    From the import users section of the docs:

    users[x].registrations [Array] Optional
    
        The list of registrations for the User.
    users[x].registrations[x].applicationId [UUID] Required
    
        The Id of the Application that this registration is for.
    
    


  • Hello @dan ,
    I tried exactly as you said but the problem still persists. Here it is the changed script. I added the registration section with my application id and also changed the factor to 1 "factor": 1

    from fusionauth.fusionauth_client import FusionAuthClient
    import math
    from dal import DAL
    from migration_parameters import MigrationParameters
    from reconsile_users import reconcile_users
    from user import User
    
    try:
        # Parse command line arguments
        migration_parameter = MigrationParameters()
    
        fa_client = FusionAuthClient(migration_parameter.fusion_auth_api_key, migration_parameter.fusion_auth_base_url)
    
        total_number_of_records = -1
    
        # Get total number of users in db
        db = DAL(configuration=migration_parameter)
        result = db.get_single("Select count(*) from users "
                               "where migrated <> 1 OR migrated IS NULL")
        total_number_of_records = result[0]
    
        total_number_of_db_chunks = math.ceil(total_number_of_records / migration_parameter.db_chunk_size)
    
        for db_index in range(0, total_number_of_records, migration_parameter.db_chunk_size):
            current_db_chunk = db.get(
                "SELECT id, birthdate, age, username, anon_url, `type`, subtype, lock_type, messages_unread, salt, "
                "password, email, "
                "first_name, middle_name, last_name, phone_number, timezone_id, `_timezone_id`, street_address,"
                "city_address, city_id, state_address, state_id, zip_code, country, lastupdate, active,"
                "gender, birthday_input, contact_pref_manage, subscription, subscription_start, subscription_expires FROM "
                "users where migrated <> 1 OR migrated IS NULL"
                , db_index, migration_parameter.db_chunk_size)
    
            total_number_of_chunk_records = len(current_db_chunk)
    
            total_number_of_fa_chunks = math.ceil(total_number_of_chunk_records / migration_parameter.fa_chunk_size)
    
            for fa_index in range(0, total_number_of_chunk_records, migration_parameter.fa_chunk_size):
    
                current_fa_chunk = current_db_chunk[fa_index:fa_index + migration_parameter.fa_chunk_size]
    
                template_request = {
                    'sendSetPasswordEmail': False,
                    'skipVerification': True,
                    "users": []
                }
                import_successfully_user = []
                import_successfully = []
                # Prepare request
                for el in current_fa_chunk:
                    try:
                        user = User.parse(result=el)
    
                        template_request['users'].append({
                            'sendSetPasswordEmail': False,
                            'skipVerification': True,
                            "active": True,
                            "birthDate": user.birthdate,
                            "data": {
                                "displayName": user.first_name + " " + user.last_name
                            },
                            "email": user.email,
                            "encryptionScheme": "gyo-password-encryptor",
                            "expiry": 1571786483322,
                            "factor": 1,
                            "firstName": user.first_name,
                            "fullName": user.first_name + " " + user.middle_name + " " + user.last_name,
                            "imageUrl": "http://65.media.tumblr.com/tumblr_l7dbl0MHbU1qz50x3o1_500.png",
                            "insertInstant": 1331449200000,
                            "lastName": user.last_name,
                            "middleName": user.middle_name,
                            "password": user.password,
                            "passwordChangeRequired": False,
                            "preferredLanguages": [
                                "en"
                            ],
                            "salt": user.salt,
                            "timezone": "America/Denver",
                            "twoFactorEnabled": False,
                            "usernameStatus": "ACTIVE",
                            "username": user.username,
                            "verified": True,
                            "registration": [
                                {
                                    "applicationId": "74e5f2cc-b485-47d5-94d7-8854747edd82"
                                }
                            ]
                        })
    
                        import_successfully.append(str(el[0]))
                        import_successfully_user.append(str(el[3]))
                    except Exception as exx:
                        print('Failed to process user with ID: ', el[0], ' with error: ', exx.args)
    
                fa_response = fa_client.import_users(template_request)
    
                if fa_response.response.status_code == 200:
                    # everything is ok
    
                    db.update_all("update users set migrated = 1 where id = %s ",
                                  [(record,) for record in import_successfully])
                    reconcile_users(migration_parameter, import_successfully_user)
                elif fa_response.response.status_code == 400 and 'unique' in fa_response.error_response['generalErrors'][0][
                    'message']:
                    # users are already imported on fusion auth but for some reasons are not stored on the db
                    db.update_all("update users set migrated = 1 where id = %s ", import_successfully)
                    reconcile_users(migration_parameter, import_successfully_user)
                else:
                    print('Failed to import users with starting ID: ', current_fa_chunk[0][0], ' ending ID: ',
                          current_fa_chunk[::len(current_fa_chunk) - 1][1][0])
    
                template_request['users'].clear()
    
    except Exception as ex:
        print(ex)
    
    


  • @dan I finally managed to find the fix, it was related to password expiry field being an expired date and the factor number had to be 1. Thanks a lot for your support.



  • That's great!


Log in to reply