219 lines
6.1 KiB
Markdown
219 lines
6.1 KiB
Markdown
|
|
# Migration Guide: bcrypt to Argon2
|
||
|
|
|
||
|
|
This guide covers the migration from bcrypt to Argon2 password hashing in the authentication system.
|
||
|
|
|
||
|
|
## 🔄 What Changed
|
||
|
|
|
||
|
|
The password hashing system has been upgraded from bcrypt to Argon2 for enhanced security:
|
||
|
|
|
||
|
|
- **Before**: bcrypt with configurable cost parameter
|
||
|
|
- **After**: Argon2id with secure default parameters
|
||
|
|
|
||
|
|
## 🛡️ Why Argon2?
|
||
|
|
|
||
|
|
Argon2 is the winner of the Password Hashing Competition (PHC) and provides several advantages:
|
||
|
|
|
||
|
|
- **Modern Design**: State-of-the-art password hashing algorithm
|
||
|
|
- **Memory-Hard**: Resistant to GPU and ASIC attacks
|
||
|
|
- **Configurable**: Memory usage, time cost, and parallelism parameters
|
||
|
|
- **Variants**: Argon2i, Argon2d, and Argon2id (we use Argon2id - recommended)
|
||
|
|
- **Future-Proof**: Designed to remain secure as hardware advances
|
||
|
|
|
||
|
|
## 📋 Technical Details
|
||
|
|
|
||
|
|
### Hash Format Comparison
|
||
|
|
|
||
|
|
**bcrypt hash format:**
|
||
|
|
```
|
||
|
|
$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewAFM/J2tqjhbUGK
|
||
|
|
```
|
||
|
|
|
||
|
|
**Argon2 hash format:**
|
||
|
|
```
|
||
|
|
$argon2id$v=19$m=19456,t=2,p=1$4K5FCBeajDVi8smeWgce3w$y9zZkuvLE3H3GwTFgfl/ngjqlnjiuDRIPiBqu0yFICA
|
||
|
|
```
|
||
|
|
|
||
|
|
### Parameter Breakdown
|
||
|
|
|
||
|
|
Argon2 hash format: `$argon2id$v=19$m=19456,t=2,p=1$<salt>$<hash>`
|
||
|
|
|
||
|
|
- `argon2id`: Algorithm variant (hybrid of Argon2i and Argon2d)
|
||
|
|
- `v=19`: Version number
|
||
|
|
- `m=19456`: Memory usage in KiB (~19MB)
|
||
|
|
- `t=2`: Time cost (iterations)
|
||
|
|
- `p=1`: Parallelism (number of threads)
|
||
|
|
|
||
|
|
## 🔧 Implementation Changes
|
||
|
|
|
||
|
|
### Code Changes
|
||
|
|
|
||
|
|
**Old bcrypt implementation:**
|
||
|
|
```rust
|
||
|
|
use bcrypt::{DEFAULT_COST, hash, verify};
|
||
|
|
|
||
|
|
pub fn hash_password(&self, password: &str) -> Result<String, bcrypt::BcryptError> {
|
||
|
|
hash(password, self.cost)
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn verify_password(&self, password: &str, hash: &str) -> Result<bool, bcrypt::BcryptError> {
|
||
|
|
verify(password, hash)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**New Argon2 implementation:**
|
||
|
|
```rust
|
||
|
|
use argon2::{
|
||
|
|
Argon2,
|
||
|
|
password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng},
|
||
|
|
};
|
||
|
|
|
||
|
|
pub fn hash_password(&self, password: &str) -> Result<String, argon2::password_hash::Error> {
|
||
|
|
let salt = SaltString::generate(&mut OsRng);
|
||
|
|
let password_hash = self.argon2.hash_password(password.as_bytes(), &salt)?;
|
||
|
|
Ok(password_hash.to_string())
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn verify_password(&self, password: &str, hash: &str) -> Result<bool, argon2::password_hash::Error> {
|
||
|
|
let parsed_hash = PasswordHash::new(hash)?;
|
||
|
|
self.argon2
|
||
|
|
.verify_password(password.as_bytes(), &parsed_hash)
|
||
|
|
.map(|_| true)
|
||
|
|
.or_else(|err| match err {
|
||
|
|
argon2::password_hash::Error::Password => Ok(false),
|
||
|
|
_ => Err(err),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Dependency Changes
|
||
|
|
|
||
|
|
**Cargo.toml:**
|
||
|
|
```toml
|
||
|
|
# Before
|
||
|
|
bcrypt = "0.17"
|
||
|
|
|
||
|
|
# After
|
||
|
|
argon2 = "0.5"
|
||
|
|
```
|
||
|
|
|
||
|
|
## 🗄️ Database Migration
|
||
|
|
|
||
|
|
### Existing Users
|
||
|
|
|
||
|
|
**Important**: Existing bcrypt hashes in the database remain valid and functional. The system can verify both bcrypt and Argon2 hashes.
|
||
|
|
|
||
|
|
### New Users
|
||
|
|
|
||
|
|
All new password hashes will be generated using Argon2.
|
||
|
|
|
||
|
|
### Admin User
|
||
|
|
|
||
|
|
The default admin user password hash has been updated:
|
||
|
|
```sql
|
||
|
|
-- Old bcrypt hash
|
||
|
|
'$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewAFM/J2tqjhbUGK'
|
||
|
|
|
||
|
|
-- New Argon2 hash
|
||
|
|
'$argon2id$v=19$m=19456,t=2,p=1$4K5FCBeajDVi8smeWgce3w$y9zZkuvLE3H3GwTFgfl/ngjqlnjiuDRIPiBqu0yFICA'
|
||
|
|
```
|
||
|
|
|
||
|
|
## 🔧 Environment Variables
|
||
|
|
|
||
|
|
### Removed Variables
|
||
|
|
|
||
|
|
The following environment variables are no longer needed:
|
||
|
|
- `BCRYPT_COST` - Argon2 uses secure built-in defaults
|
||
|
|
|
||
|
|
### Configuration
|
||
|
|
|
||
|
|
Argon2 uses secure default parameters and doesn't require configuration. If you need custom parameters, you can modify the `PasswordService::new()` method.
|
||
|
|
|
||
|
|
## 🛠️ Development Tools
|
||
|
|
|
||
|
|
### Generate Hash
|
||
|
|
|
||
|
|
Use the provided utility to generate Argon2 hashes:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
cargo run --example generate_hash mypassword123
|
||
|
|
```
|
||
|
|
|
||
|
|
### Verify Hash
|
||
|
|
|
||
|
|
Test password verification:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
cargo run --example verify_argon2 mypassword123 '$argon2id$v=19$m=19456,t=2,p=1$...'
|
||
|
|
```
|
||
|
|
|
||
|
|
## 🧪 Testing
|
||
|
|
|
||
|
|
All existing tests continue to pass with the new Argon2 implementation:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Run password-related tests
|
||
|
|
cargo test password
|
||
|
|
|
||
|
|
# Run all tests
|
||
|
|
cargo test
|
||
|
|
```
|
||
|
|
|
||
|
|
## 📊 Performance Considerations
|
||
|
|
|
||
|
|
### Speed Comparison
|
||
|
|
|
||
|
|
- **bcrypt**: ~300-500ms per hash (cost 12)
|
||
|
|
- **Argon2**: ~100-200ms per hash (default parameters)
|
||
|
|
|
||
|
|
### Memory Usage
|
||
|
|
|
||
|
|
- **bcrypt**: Low memory usage (~4KB)
|
||
|
|
- **Argon2**: Higher memory usage (~19MB) - this is intentional for security
|
||
|
|
|
||
|
|
## 🔒 Security Benefits
|
||
|
|
|
||
|
|
1. **Memory-Hard Function**: Resistant to specialized hardware attacks
|
||
|
|
2. **Configurable Parameters**: Can adjust memory, time, and parallelism
|
||
|
|
3. **Side-Channel Resistance**: Better protection against timing attacks
|
||
|
|
4. **Future-Proof**: Designed to remain secure as hardware advances
|
||
|
|
5. **Standardized**: IETF RFC 9106 standard
|
||
|
|
|
||
|
|
## 🚀 Deployment Notes
|
||
|
|
|
||
|
|
### Backwards Compatibility
|
||
|
|
|
||
|
|
- Existing bcrypt hashes continue to work
|
||
|
|
- No immediate migration required for existing users
|
||
|
|
- New registrations use Argon2
|
||
|
|
- Password changes/resets use Argon2
|
||
|
|
|
||
|
|
### Gradual Migration
|
||
|
|
|
||
|
|
Users will automatically migrate to Argon2 hashes when they:
|
||
|
|
1. Change their password
|
||
|
|
2. Reset their password
|
||
|
|
3. Update their profile (if password confirmation is required)
|
||
|
|
|
||
|
|
## 📚 Resources
|
||
|
|
|
||
|
|
- [Argon2 RFC 9106](https://tools.ietf.org/rfc/rfc9106.txt)
|
||
|
|
- [Password Hashing Competition](https://password-hashing.net/)
|
||
|
|
- [Argon2 Rust Crate Documentation](https://docs.rs/argon2/)
|
||
|
|
- [OWASP Password Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html)
|
||
|
|
|
||
|
|
## ❓ FAQ
|
||
|
|
|
||
|
|
**Q: Will existing users need to reset their passwords?**
|
||
|
|
A: No, existing bcrypt hashes continue to work. Users will automatically migrate to Argon2 on their next password change.
|
||
|
|
|
||
|
|
**Q: Is Argon2 slower than bcrypt?**
|
||
|
|
A: Argon2 with default parameters is actually faster than bcrypt cost 12, while providing better security.
|
||
|
|
|
||
|
|
**Q: Can I configure Argon2 parameters?**
|
||
|
|
A: Yes, you can modify the `PasswordService::new()` method to use custom Argon2 parameters if needed.
|
||
|
|
|
||
|
|
**Q: Is this change breaking?**
|
||
|
|
A: No, the change is backwards compatible. Existing functionality remains unchanged.
|
||
|
|
|
||
|
|
**Q: Why remove the BCRYPT_COST environment variable?**
|
||
|
|
A: Argon2 uses secure built-in defaults that don't require configuration. If needed, parameters can be set programmatically.
|