[New Feature] Tried Specifying Password Hashes in Amazon Cognito CSV Import
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.
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
-
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 forPasswordHashingAlgorithm. Specifying it results in the following error.ParamValidationError: Parameter validation failed: Unknown parameter in input: "PasswordHashingAlgorithm", must be one of: JobName, UserPoolId, CloudWatchLogsRoleArnIn 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. -
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-headerin the header, leaving unnecessary values blank. -
For pools with
username-attributes: email, specify the email address incognito:username
Setting anything other than an email address incognito:usernamefor 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. -
SRP authentication cannot be used immediately after import
UsingUSER_SRP_AUTHbefore 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_AUTHorADMIN_USER_PASSWORD_AUTHfor 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.
