selfsimilar/drupal7_password_hasher
PHP package for verifying and generating Drupal 7-compatible password hashes. Useful for migrating users to Laravel or other apps while preserving existing credentials, with support for Drupal’s phpass-based hashing format and validation against stored hashes.
Installation
composer require selfsimilar/drupal7_password_hasher
Add to composer.json if not auto-loaded:
"autoload": {
"psr-4": {
"App\\": "app/",
"SelfSimilar\\Drupal7PasswordHasher\\": "vendor/selfsimilar/drupal7_password_hasher/src/"
}
}
Run composer dump-autoload.
First Use Case Verify a Drupal 7 hashed password against a plaintext input:
use SelfSimilar\Drupal7PasswordHasher\Drupal7PasswordHasher;
$hasher = new Drupal7PasswordHasher();
$isValid = $hasher->checkPassword('user_input_password', '$S$...drupal7_hash...');
Hashing a New Password
$hash = $hasher->hashPassword('plaintext_password');
Drupal7PasswordHasher.php for core logic.tests/ for edge cases (e.g., empty strings, malformed hashes).uid, pass) from Drupal 7 via users table or hook_user().foreach ($drupalUsers as $user) {
$hasher = new Drupal7PasswordHasher();
$isValid = $hasher->checkPassword($user['pass'], $user['hash']); // Legacy check
$newHash = $hasher->hashPassword($user['pass']); // Re-hash for new system
}
users table with password column:
User::create([
'name' => $user['name'],
'password' => $newHash,
// Other fields...
]);
Hasher interface (Laravel 8+) via a facade or service binding:
// app/Providers/AppServiceProvider.php
public function register()
{
$this->app->bind(\Illuminate\Contracts\Hashing\Hasher::class, function ($app) {
return new Drupal7PasswordHasher();
});
}
Queue::push(new HashDrupal7Users($drupalUsersChunk));
$hash = app(\Illuminate\Contracts\Hashing\Hasher::class)->make($password);
| Scenario | Implementation |
|---|---|
| Legacy Login Support | Override AuthenticatesUsers trait to use Drupal7PasswordHasher::checkPassword(). |
| Password Reset Tokens | Hash tokens with hashPassword() for consistency. |
| Audit Logs | Store original Drupal 7 hashes in password_history for compliance. |
Hash Format Sensitivity
$S$ prefix for hashes. Reject malformed hashes early:
if (!preg_match('/^\$S\$[1-9]\$[a-zA-Z0-9\.\/]{53}$/', $hash)) {
throw new \InvalidArgumentException('Invalid Drupal 7 hash format.');
}
Hash::check() won’t work—always use this package for Drupal 7 hashes.Performance
Salt Handling
Deprecation Risk
bcrypt) post-migration.user_hash_password() for comparison:
// PHP CLI test:
require 'vendor/autoload.php';
$hasher = new \SelfSimilar\Drupal7PasswordHasher\Drupal7PasswordHasher();
var_dump($hasher->checkPassword('test', '$S$...'));
\Log::debug('Hash length:', strlen($hash)); // Avoid logging full hashes.
Custom Hash Verification Extend the class to add pre-check logic (e.g., rate limiting):
class CustomDrupal7Hasher extends Drupal7PasswordHasher {
public function checkPassword($plain, $hashed) {
if ($this->isBruteForceAttempt($plain)) {
throw new \RuntimeException('Too many attempts.');
}
return parent::checkPassword($plain, $hashed);
}
}
Hybrid Hashing Support both Drupal 7 and Laravel hashes in a single system:
class HybridHasher {
public function check($plain, $hashed) {
if (str_starts_with($hashed, '$2y$')) { // Laravel bcrypt
return \Hash::check($plain, $hashed);
}
return (new Drupal7PasswordHasher())->checkPassword($plain, $hashed);
}
}
Configuration Override salt length or iterations (not recommended unless necessary):
$hasher = new Drupal7PasswordHasher(8, 1); // 8-char salt, 1 iteration (default: 53, 1)
Hash facade, bind the package’s hasher to avoid conflicts:
// config/auth.php
'defaults' => [
'password' => \SelfSimilar\Drupal7PasswordHasher\Drupal7PasswordHasher::class,
],
$mockHasher = $this->createMock(Drupal7PasswordHasher::class);
$mockHasher->method('checkPassword')->willReturn(true);
$this->app->instance(Drupal7PasswordHasher::class, $mockHasher);
How can I help you explore Laravel packages today?