[New Feature] Tried Specifying Password Hashes in Amazon Cognito CSV Import

[New Feature] Tried Specifying Password Hashes in Amazon Cognito CSV Import

Amazon Cognito's user import feature has been updated to allow importing password hashes included in CSV files. Previously, a password reset was required after importing, but users can now sign in immediately with their existing passwords. We imported users using two algorithms, bcrypt and PBKDF2_SHA256, and confirmed that authentication was successful.
2026.07.16

This page has been translated by machine translation. View original

Introduction

Amazon Cognito's user import feature has been updated to allow importing users with password hashes included in a CSV file. With the traditional CSV import, the user status after import would be RESET_REQUIRED, requiring a mandatory password reset on first sign-in. When importing with password hashes included in the CSV, the user status becomes CONFIRMED, allowing users to sign in immediately with their existing passwords.

https://aws.amazon.com/jp/about-aws/whats-new/2026/07/amazon-cognito-password-hash-import/

In this article, we covered 2 of the 4 supported algorithms — bcrypt and PBKDF2_SHA256 — and confirmed that password hashes can be set in a CSV file for import, allowing authentication with existing passwords.

Verification Details

Verification Environment

Item Value
Region ap-northeast-1
User Pool username-attributes: email, with email verification
App Client No secret, ALLOW_USER_PASSWORD_AUTH / ALLOW_ADMIN_USER_PASSWORD_AUTH enabled
AWS CLI v2.35.24
boto3 / botocore 1.43.49
Verified algorithms BCRYPT, PBKDF2_SHA256

Prerequisites

We created a user pool with email address as the sign-in attribute and email verification enabled.

aws cognito-idp create-user-pool \
  --pool-name password-hash-import-test \
  --username-attributes email \
  --auto-verified-attributes email \
  --policies '{"PasswordPolicy":{"MinimumLength":8,"RequireUppercase":true,"RequireLowercase":true,"RequireNumbers":true,"RequireSymbols":true}}' \
  --region ap-northeast-1
{
    "UserPool": {
        "Id": "ap-northeast-1_XXXXXXXXX",
        "Name": "password-hash-import-test",
        "UsernameAttributes": ["email"],
        "AutoVerifiedAttributes": ["email"]
    }
}

Since users immediately after import cannot use SRP authentication, it is necessary to enable an authentication flow that sends passwords directly. We created an app client with ALLOW_USER_PASSWORD_AUTH and ALLOW_ADMIN_USER_PASSWORD_AUTH enabled. No secret is issued.

aws cognito-idp create-user-pool-client \
  --user-pool-id ap-northeast-1_XXXXXXXXX \
  --client-name password-hash-import-client \
  --no-generate-secret \
  --explicit-auth-flows ALLOW_USER_PASSWORD_AUTH ALLOW_ADMIN_USER_PASSWORD_AUTH ALLOW_REFRESH_TOKEN_AUTH \
  --region ap-northeast-1
{
    "UserPoolClient": {
        "UserPoolId": "ap-northeast-1_XXXXXXXXX",
        "ClientId": "xxxxxxxxxxxxxxxxxxxxxxxxxx",
        "ClientName": "password-hash-import-client",
        "ExplicitAuthFlows": [
            "ALLOW_USER_PASSWORD_AUTH",
            "ALLOW_ADMIN_USER_PASSWORD_AUTH",
            "ALLOW_REFRESH_TOKEN_AUTH"
        ]
    }
}

We created an IAM role for the import job to write to CloudWatch Logs. The trust policy allows assumption from cognito-idp.amazonaws.com and grants write permissions to CloudWatch Logs.

cat > trust-policy.json <<'EOF'
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {"Service": "cognito-idp.amazonaws.com"},
    "Action": "sts:AssumeRole"
  }]
}
EOF

aws iam create-role \
  --role-name cognito-import-cloudwatch-role \
  --assume-role-policy-document file://trust-policy.json

aws iam put-role-policy \
  --role-name cognito-import-cloudwatch-role \
  --policy-name cognito-import-logs \
  --policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["logs:CreateLogGroup","logs:CreateLogStream","logs:DescribeLogStreams","logs:PutLogEvents"],"Resource":"arn:aws:logs:*:*:*"}]}'
{
    "Role": {
        "RoleName": "cognito-import-cloudwatch-role",
        "Arn": "arn:aws:iam::123456789012:role/cognito-import-cloudwatch-role"
    }
}

bcrypt Hash Import

First, we generated a hash of the test password TestPassword123! using bcrypt with a cost of 10. The maximum cost accepted by Cognito is 10.

import bcrypt

password = b'TestPassword123!'
salt = bcrypt.gensalt(rounds=10)
hashed = bcrypt.hashpw(password, salt)
print(hashed.decode())
# Example output: $2b$10$6utAl7aqnqX9VP4tyUsY2Ol9eciwln7Wx71GFPBy1I1P8xpKgfUu6

The CSV header must be exactly as obtained from get-csv-header for the target user pool.

get-csv-header execution result
aws cognito-idp get-csv-header \
  --user-pool-id ap-northeast-1_XXXXXXXXX \
  --region ap-northeast-1
{
    "UserPoolId": "ap-northeast-1_XXXXXXXXX",
    "CSVHeader": [
        "profile", "address", "birthdate", "gender", "preferred_username",
        "updated_at", "website", "picture", "phone_number", "phone_number_verified",
        "zoneinfo", "locale", "email", "email_verified", "given_name", "family_name",
        "middle_name", "name", "nickname", "cognito:mfa_enabled", "cognito:username",
        "password_hash"
    ]
}

The password_hash column is included at the end. We included all retrieved columns in the header and set the generated hash in password_hash. For a pool with username-attributes: email, the email address is also set in cognito:username. For details on the CSV format, refer to the official documentation.

profile,address,birthdate,gender,preferred_username,updated_at,website,picture,phone_number,phone_number_verified,zoneinfo,locale,email,email_verified,given_name,family_name,middle_name,name,nickname,cognito:mfa_enabled,cognito:username,password_hash
,,,,,,,,,,,,bcrypt-user-01@example.com,TRUE,,,,,,,bcrypt-user-01@example.com,$2b$10$6utAl7aqnqX9VP4tyUsY2Ol9eciwln7Wx71GFPBy1I1P8xpKgfUu6
,,,,,,,,,,,,bcrypt-user-02@example.com,TRUE,,,,,,,bcrypt-user-02@example.com,$2b$10$6utAl7aqnqX9VP4tyUsY2Ol9eciwln7Wx71GFPBy1I1P8xpKgfUu6

For CreateUserImportJob, specify BCRYPT in the new PasswordHashingAlgorithm parameter. Since AWS CLI v2.35.24 and boto3 / botocore 1.43.49 used at the time of verification did not support this parameter, we called the HTTP API directly using botocore's SigV4 signing. Below is the complete code that was executed.

import json
import urllib.request
from botocore.auth import SigV4Auth
from botocore.awsrequest import AWSRequest
from botocore.session import Session

region = 'ap-northeast-1'
service = 'cognito-idp'
endpoint = f'https://cognito-idp.{region}.amazonaws.com/'

session = Session()
credentials = session.get_credentials().get_frozen_credentials()

payload = json.dumps({
    'JobName': 'bcrypt-import-test-3',
    'UserPoolId': 'ap-northeast-1_XXXXXXXXX',
    'CloudWatchLogsRoleArn': 'arn:aws:iam::123456789012:role/cognito-import-cloudwatch-role',
    'PasswordHashingAlgorithm': 'BCRYPT',
})

headers = {
    'Content-Type': 'application/x-amz-json-1.1',
    'X-Amz-Target': 'AWSCognitoIdentityProviderService.CreateUserImportJob',
}

request = AWSRequest(method='POST', url=endpoint, data=payload, headers=headers)
SigV4Auth(credentials, service, region).add_auth(request)

req = urllib.request.Request(
    endpoint,
    data=payload.encode('utf-8'),
    headers=dict(request.headers),
    method='POST',
)
with urllib.request.urlopen(req) as response:
    print(response.read().decode())

The response included a pre-signed URL for uploading the CSV and the PasswordHashingAlgorithm. We confirmed that the specified BCRYPT was returned.

CreateUserImportJob response
{
    "UserImportJob": {
        "JobName": "bcrypt-import-test-3",
        "JobId": "import-XXXXXXXXXX",
        "UserPoolId": "ap-northeast-1_XXXXXXXXX",
        "PreSignedUrl": "https://cognito-idp-user-import-pdx.s3.us-west-2.amazonaws.com/<masked>",
        "Status": "Created",
        "CloudWatchLogsRoleArn": "arn:aws:iam::123456789012:role/cognito-import-cloudwatch-role",
        "ImportedUsers": 0,
        "SkippedUsers": 0,
        "FailedUsers": 0,
        "PasswordHashingAlgorithm": "BCRYPT"
    }
}

We uploaded the CSV to the returned pre-signed URL.

curl -sS -o /dev/null -w '%{http_code}\n' \
  --request PUT \
  --upload-file bcrypt-users.csv \
  'https://cognito-idp-user-import-pdx.s3.us-west-2.amazonaws.com/<masked>'
200

After uploading, we started the job using the AWS CLI.

aws cognito-idp start-user-import-job \
  --user-pool-id ap-northeast-1_XXXXXXXXX \
  --job-id import-XXXXXXXXXX \
  --region ap-northeast-1
{
    "UserImportJob": {
        "JobId": "import-XXXXXXXXXX",
        "Status": "Pending",
        "PasswordHashingAlgorithm": "BCRYPT"
    }
}

Checking the job after completion showed that 2 users were successfully imported.

aws cognito-idp describe-user-import-job \
  --user-pool-id ap-northeast-1_XXXXXXXXX \
  --job-id import-XXXXXXXXXX \
  --region ap-northeast-1
{
    "UserImportJob": {
        "JobName": "bcrypt-import-test-3",
        "JobId": "import-XXXXXXXXXX",
        "Status": "Succeeded",
        "ImportedUsers": 2,
        "SkippedUsers": 0,
        "FailedUsers": 0,
        "PasswordHashingAlgorithm": "BCRYPT",
        "CompletionMessage": "Import Job Completed Successfully."
    }
}

The UserStatus of the imported users was CONFIRMED.

aws cognito-idp admin-get-user \
  --user-pool-id ap-northeast-1_XXXXXXXXX \
  --username bcrypt-user-01@example.com \
  --region ap-northeast-1
{
    "Username": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "UserStatus": "CONFIRMED",
    "Enabled": true,
    "UserAttributes": [
        {"Name": "email", "Value": "bcrypt-user-01@example.com"},
        {"Name": "email_verified", "Value": "true"}
    ]
}

Running ADMIN_USER_PASSWORD_AUTH with the password used before import successfully retrieved tokens.

aws cognito-idp admin-initiate-auth \
  --user-pool-id ap-northeast-1_XXXXXXXXX \
  --client-id xxxxxxxxxxxxxxxxxxxxxxxxxx \
  --auth-flow ADMIN_USER_PASSWORD_AUTH \
  --auth-parameters USERNAME=bcrypt-user-01@example.com,PASSWORD=TestPassword123! \
  --region ap-northeast-1
{
    "AuthenticationResult": {
        "AccessToken": "<masked>",
        "ExpiresIn": 3600,
        "TokenType": "Bearer",
        "RefreshToken": "<masked>",
        "IdToken": "<masked>"
    },
    "ChallengeParameters": {}
}

An incorrect password returned NotAuthorizedException.

aws cognito-idp admin-initiate-auth \
  --user-pool-id ap-northeast-1_XXXXXXXXX \
  --client-id xxxxxxxxxxxxxxxxxxxxxxxxxx \
  --auth-flow ADMIN_USER_PASSWORD_AUTH \
  --auth-parameters USERNAME=bcrypt-user-01@example.com,PASSWORD=WrongPassword456! \
  --region ap-northeast-1
An error occurred (NotAuthorizedException) when calling the AdminInitiateAuth operation: Incorrect username or password.

PBKDF2_SHA256 Hash Import

We verified the same flow with PBKDF2_SHA256. The iteration count was set to the maximum value of 600000, and hashes were generated in the following format. The format accepted by Cognito is $pbkdf2-sha256$iterations$base64salt$base64hash. In this verification, we successfully imported with the base64 padding (=) stripped.

import hashlib, base64, os

password = b'TestPassword123!'
salt = os.urandom(16)
iterations = 600000
dk = hashlib.pbkdf2_hmac('sha256', password, salt, iterations)

salt_b64 = base64.b64encode(salt).rstrip(b'=').decode()
hash_b64 = base64.b64encode(dk).rstrip(b'=').decode()
hash_str = f"$pbkdf2-sha256${iterations}${salt_b64}${hash_b64}"
print(hash_str)
# Example output: $pbkdf2-sha256$600000$dFmpDGYWnTMuSn2wC3qo7Q$+1ITLXTq5iiV2SbQQ6loZ0DHz21ZktaFJMNemioCfcE

When creating the import job, specify PBKDF2_SHA256 in PasswordHashingAlgorithm.

payload = json.dumps({
    'JobName': 'pbkdf2-import-test',
    'UserPoolId': 'ap-northeast-1_XXXXXXXXX',
    'CloudWatchLogsRoleArn': 'arn:aws:iam::123456789012:role/cognito-import-cloudwatch-role',
    'PasswordHashingAlgorithm': 'PBKDF2_SHA256',
})

The steps from CSV upload to authentication testing were carried out in the same way as with bcrypt.

Verification Item bcrypt PBKDF2_SHA256
Import Job Status Succeeded Succeeded
ImportedUsers 2 2
FailedUsers 0 0
User UserStatus CONFIRMED CONFIRMED
Authentication with correct password SUCCESS (token retrieved) SUCCESS (token retrieved)
Authentication with wrong password NotAuthorizedException NotAuthorizedException
PasswordHashingAlgorithm in response BCRYPT PBKDF2_SHA256

Notes and Tips

  1. AWS CLI / boto3 at the time of verification did not support PasswordHashingAlgorithm
    The AWS CLI v2.35.24 and boto3 / botocore 1.43.49 used at the time of verification (2026-07-16) did not include the input model for PasswordHashingAlgorithm. Specifying it results in the following error.

    ParamValidationError: Parameter validation failed:
    Unknown parameter in input: "PasswordHashingAlgorithm", must be one of: JobName, UserPoolId, CloudWatchLogsRoleArn
    

    In this verification, we called the HTTP API directly using botocore's SigV4Auth. Depending on the version of CLI / SDK used, it may be possible to specify this as an official parameter.

  2. The CSV header must include all columns retrieved by get-csv-header
    Using a CSV with only the necessary attributes will cause the import to fail due to a header mismatch.

    The header in the CSV file does not match the expected headers.
    

    Include all columns returned by get-csv-header in the header, leaving unnecessary values blank.

  3. For pools with username-attributes: email, specify the email address in cognito:username
    Setting anything other than an email address in cognito:username for this pool configuration will cause the import to fail.

    Username should be an email.
    

    Set an email address consistent with the user pool's sign-in attributes in cognito:username.

  4. SRP authentication cannot be used immediately after import
    Using USER_SRP_AUTH before the first sign-in will result in an authentication failure.

    An error occurred (NotAuthorizedException) when calling the InitiateAuth operation: Incorrect username or password.
    

    Use USER_PASSWORD_AUTH or ADMIN_USER_PASSWORD_AUTH for the first sign-in. After the first sign-in, SRP authentication becomes available as well.

Supported Algorithms and Parameter Limits

In addition to BCRYPT and PBKDF2_SHA256 verified on actual hardware, SCRYPT and ARGON2ID are also supported. The formats and parameter limits are documented in the official documentation.

Algorithm Format Parameter Limits
BCRYPT $2<a/b/x/y>$[cost]$[22-char salt][31-char hash] cost ≤ 10
SCRYPT N$r$p$hexSalt$hexHash N ≤ 65536, r ≤ 8, p ≤ 1
ARGON2ID $argon2id$v=N$m=M,t=T,p=P$salt$hash m ≤ 19456, t ≤ 2, p ≤ 1
PBKDF2_SHA256 $pbkdf2-sha256$iterations$salt$hash iterations ≤ 600000

If the hashes from the source system do not match a supported algorithm or supported format, or if they were generated with parameters exceeding these limits, they cannot be imported, so verification in advance is necessary.

Summary

Amazon Cognito's CSV import now supports specifying password hashes. After verifying with both bcrypt and PBKDF2_SHA256, we confirmed that users can be imported with CONFIRMED status for both algorithms, and authentication with pre-import passwords succeeded.

As long as hashes meet the supported algorithm, supported format, and parameter limits, users can be migrated from an existing system to Cognito without requiring a mass password reset.

However, the scope of this feature is "improved migration from other systems to Cognito." There is still no means provided to export password hashes from Cognito. Therefore, portability such as re-migrating to another service after migration, or performing backup/restore including password hashes, is not guaranteed.

When adopting Cognito as a user authentication platform, we recommend evaluating not only the import feature but also non-functional requirements such as API execution rate limits.

https://dev.classmethod.jp/articles/cognito-provisioned-limit-service-quota-increase-load-test/

Share this article

AWSのお困り事はクラスメソッドへ