Skip to content

bcrypt_sha256 - #24073

Draft
Sjord wants to merge 4 commits into
php:masterfrom
Sjord:bcrypt_sha256
Draft

Sjord wants to merge 4 commits into
php:masterfrom
Sjord:bcrypt_sha256

Conversation

@Sjord

@Sjord Sjord commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

No description provided.

Comment thread ext/standard/php_password.h Outdated

#define PHP_PASSWORD_DEFAULT PHP_PASSWORD_BCRYPT
#define PHP_PASSWORD_BCRYPT_COST 12
#define PHP_PASSWORD_BCRYPT_SHA256_COST 12

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be simpler to just reuse PHP_PASSWORD_BCRYPT_COST here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes sense to me to have a separate define with an adjusted name, but it should just "pass through" to the other one.

Comment thread ext/standard/password.c

/* If the key is longer than the block size (64), hash it first. Our key is
* always the 22-byte salt, so this branch is never taken in practice. */
if (key_len > 64) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add UNEXPECTED()?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No

@Sjord

Sjord commented Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

Ping @TimWolla @narfbg. I vibe-coded bcrypt sha256 support for password_hash / _verify.

}

// Unlike bcrypt, a NUL byte is allowed: the HMAC pre-hash removes the quirk.
var_dump(password_verify("foo\x00bar", password_hash("foo\x00bar", PASSWORD_BCRYPT_SHA256)));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use the minimal acceptable cost here to keep the test fast.

Comment on lines +20 to +22
// The string ident works too
$h2 = password_hash("foo", "bcrypt-sha256");
var_dump(password_verify("foo", $h2));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think testing this is useful. This is not how the API is supposed to be used.

<?php
//-=-=-=-

$h10 = password_hash("foo", PASSWORD_BCRYPT_SHA256, ["cost" => 10]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a smaller cost for performance.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test should likely be marked as slow.

Comment thread ext/standard/password.c
(c >= '0' && c <= '9');
}

static void php_password_hmac_sha256(const unsigned char *key, size_t key_len,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We likely should add an internal HMAC API to ext/hash instead of reimplementing it everywhere and to avoid going through the userland API.

Comment thread ext/standard/password.c Outdated
if (len < 82 || len > 83) {
return false;
}
if (memcmp(h, PHP_PASSWORD_BCRYPT_SHA256_PREFIX, PHP_PASSWORD_BCRYPT_SHA256_PREFIX_LEN) != 0) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

zend_string_starts_with_literal(hash, PHP_PASSWORD_BCRYPT_SHA256_PREFIX)

Comment thread ext/standard/password.c Outdated
Comment on lines +297 to +298
size_t i;
int c;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reduce the scope as much as possible.

Comment thread ext/standard/password.c Outdated
if (!php_password_bcrypt_sha256_parse(hash, &cost, &salt, &digest)) {
return true;
}
if (options && (znew_cost = zend_hash_str_find(options, "cost", sizeof("cost") - 1)) != NULL) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (options && (znew_cost = zend_hash_str_find(options, "cost", sizeof("cost") - 1)) != NULL) {
if (options && (znew_cost = zend_hash_str_find(options, "cost", strlen("cost"))) != NULL) {

For new code.

Comment thread ext/standard/password.c

/* Relabel the $2y$ result into the bcrypt-sha256 format. The digest is the
* last 31 characters of the 60-byte bcrypt output. */
out_len = snprintf(out, sizeof(out), "$bcrypt-sha256$v=2,t=2b,r=%" ZEND_LONG_FMT_SPEC "$%s$%s",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use zend_strpprintf() to directly print into a zend_string.

Comment thread ext/standard/password.c
return false;
}

/* Constant-time comparison of the 31-byte digests. The salt portion of the

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

php_safe_bcmp

Sjord added 3 commits October 2, 2026 18:21
Don't accept leading zeroes on rounds int.
Changed bad test vectors to be more like v2 good hashes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants