Description
The bcrypt gem silently trims input strings longer than 72 characters during password hashing, which can lead to unexpected behavior.
Specifically, when a string longer than 72 characters is provided, BCrypt::Password.create truncates the input to 72 characters without warning, allowing different inputs to produce the same hash. This can potentially enable password verification bypasses if an attacker uses a different string that matches the first 72 characters of the original input. (similar behavior has been posted by OKTA: https://trust.okta.com/security-advisories/okta-ad-ldap-delegated-authentication-username/ )
Steps to Reproduce
- Install the bcrypt gem (gem install bcrypt).
- Run the following Ruby script:
require 'bcrypt'
cost = 12
userid = "b91fa9b4-69f1-4779-8d45-73f8653057f3" # 36 characters
username = "my.very.long.username.with.more.characters@kondukto.io" # 47 characters
password = "randomStrongPassword" # 20 characters
input = userid + username + password # Total: 103 characters
begin
my_password = BCrypt::Password.create(input, cost: cost)
puts "Hashed password: #{my_password}"
# Validate the correct password
is_valid = BCrypt::Password.new(my_password) == input
puts "Correct password validation: #{is_valid ? 'Success' : 'Failure'}"
# Validate with a different password
wrong_password = "AAAAAAAAAAAAAAAAAAAA"
bypass_input = userid + username + wrong_password # Also 103 characters
is_valid = BCrypt::Password.new(my_password) == bypass_input
puts "Bypass password validation: #{is_valid ? 'Success' : 'Failure'}"
rescue BCrypt::Errors::InvalidCost, BCrypt::Errors::InvalidSalt => e
puts "Error creating password: #{e.message}"
end
- Observe the output:
└> ruby main.rb
Hashed password 1: $2a$12$nefsa21AluV1BF2EXx6Y4.u6ZV4KT3c1ZWXLIOpWV9KZZ2Y1lGQmO
Password validation: Success
Password validation: Success
Expected Behavior
- The bcrypt gem should either:
- Raise an error when an input string exceeds 72 characters, indicating that the input is too long.
- Explicitly document the 72-character limit and warn about truncation in the gem's documentation.
- The validation of bypass_input (which differs from input after the first 72 characters) should return Failure, as it is a different string.
Actual Behavior
- The bcrypt gem silently trims the input string to 72 characters.
- Both input (103 characters) and bypass_input (103 characters, differing after 72 characters) validate successfully against the same hash, indicating that only the first 72 characters are considered.
- No warning or error is raised about the truncation.
Suggested Fix
- Add a check in BCrypt::Password.create to raise an error (e.g., BCrypt::Errors::InputTooLong) if the input exceeds 72 characters. (this is how other programming languages handles it)
- Alternatively, log a warning when truncation occurs to alert developers.
- Update the gem's documentation to clearly state the 72-character limit and the truncation behavior
Environment
- Ruby version: ruby 3.2.3 (2024-01-18 revision 52bb2ac0a6) [x86_64-linux-gnu]
- bcrypt version: 3.1.13
- Operating System: Ubuntu 24.04.2 LTS
References
Reactions are currently unavailable
Description
The bcrypt gem silently trims input strings longer than 72 characters during password hashing, which can lead to unexpected behavior.
Specifically, when a string longer than 72 characters is provided, BCrypt::Password.create truncates the input to 72 characters without warning, allowing different inputs to produce the same hash. This can potentially enable password verification bypasses if an attacker uses a different string that matches the first 72 characters of the original input. (similar behavior has been posted by OKTA: https://trust.okta.com/security-advisories/okta-ad-ldap-delegated-authentication-username/ )
Steps to Reproduce
require 'bcrypt' cost = 12 userid = "b91fa9b4-69f1-4779-8d45-73f8653057f3" # 36 characters username = "my.very.long.username.with.more.characters@kondukto.io" # 47 characters password = "randomStrongPassword" # 20 characters input = userid + username + password # Total: 103 characters begin my_password = BCrypt::Password.create(input, cost: cost) puts "Hashed password: #{my_password}" # Validate the correct password is_valid = BCrypt::Password.new(my_password) == input puts "Correct password validation: #{is_valid ? 'Success' : 'Failure'}" # Validate with a different password wrong_password = "AAAAAAAAAAAAAAAAAAAA" bypass_input = userid + username + wrong_password # Also 103 characters is_valid = BCrypt::Password.new(my_password) == bypass_input puts "Bypass password validation: #{is_valid ? 'Success' : 'Failure'}" rescue BCrypt::Errors::InvalidCost, BCrypt::Errors::InvalidSalt => e puts "Error creating password: #{e.message}" endExpected Behavior
Actual Behavior
Suggested Fix
Environment
References