wisetail/bcryptswift
A secure, modern Swift implementation of the bcrypt password hashing algorithm.
Attribution
This project is based on the original BCryptSwift by Felipe Florencio Garcia (https://github.com/felipeflorencio/BCryptSwift), which in turn was based on JKBCrypt by Joe Kramer (https://github.com/meanjoe45/JKBCrypt).
Version 2.0 has been updated and is maintained by Dave Friedel / Wisetail.
[[CI Status]](https://github.com/wisetail/BCryptSwift/actions) [[Version]](http://cocoapods.org/pods/BCryptSwift) [[License]](http://cocoapods.org/pods/BCryptSwift) [[Platform]](http://cocoapods.org/pods/BCryptSwift) [[Swift Package Manager]](https://github.com/apple/swift-package-manager) [[Swift Version]](https://swift.org)
Features
- Secure: Uses
SecRandomCopyBytesfor cryptographically secure random number generation - Modern Swift: Fully updated for Swift 5+ with proper error handling
- Memory Safe: Implements secure memory cleanup for sensitive data
- Thread Safe: Safe for concurrent use
- Async/Await: Support for modern async Swift APIs
- Timing Attack Resistant: Implements constant-time string comparison
- Well Tested: Comprehensive test suite including security tests
- Compatible: Maintains backward compatibility with existing code
Installation
Note: This is a fork of the original BCryptSwift. For CocoaPods, use
WisetailBCryptSwift. For Swift Package Manager, the module name remainsBCryptSwift.
Swift Package Manager (Recommended)
Add the following to your Package.swift file:
dependencies: [
.package(url: "https://github.com/wisetail/BCryptSwift.git", from: "2.0.1")
]Or in Xcode:
- File → Add Package Dependencies
- Enter package URL:
https://github.com/wisetail/BCryptSwift.git - Select version: "Up to Next Major" → "2.0.1"
CocoaPods
Add the following line to your Podfile:
pod 'WisetailBCryptSwift'Usage
Modern API (Recommended)
import BCryptSwift
// Using the modern API with proper error handling
do {
// Generate a salt with default configuration (10 rounds)
let salt = try BCryptSwiftModern.generateSalt()
// Hash a password
let hashedPassword = try BCryptSwiftModern.hashPassword("mySecurePassword", withSalt: salt)
// Verify a password
let isValid = try BCryptSwiftModern.verifyPassword("mySecurePassword", matchesHash: hashedPassword)
print("Password is valid: \(isValid)")
} catch let error as BCryptError {
// Handle specific BCrypt errors
print("BCrypt error: \(error.localizedDescription)")
} catch {
// Handle other errors
print("Unexpected error: \(error)")
}Configuration Options
// Use different security levels
let defaultConfig = BCryptConfiguration.default // 10 rounds
let highSecurity = BCryptConfiguration.highSecurity // 12 rounds
let testing = BCryptConfiguration.testing // 4 rounds (fast, for tests only)
// Custom configuration
let customConfig = BCryptConfiguration(
rounds: 14, // 4-31
version: .v2a, // .v2a, .v2b, or .v2y
maxPasswordLength: 72 // BCrypt maximum
)
// Generate salt with custom configuration
let salt = try BCryptSwiftModern.generateSalt(config: customConfig)Async/Await Support
// For iOS 13.0+, macOS 10.15+, tvOS 13.0+, watchOS 6.0+
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
func hashPasswordAsync() async throws {
// Hash password asynchronously
let hashedPassword = try await BCryptSwiftModern.hashPasswordAsync("myPassword")
// Verify password asynchronously
let isValid = try await BCryptSwiftModern.verifyPasswordAsync("myPassword",
matchesHash: hashedPassword)
}Legacy API (Backward Compatible)
// For existing code - returns optionals instead of throwing errors
let salt = BCryptSwift.generateSalt()
let hash = BCryptSwift.hashPassword("password", withSalt: salt)
let isValid = BCryptSwift.verifyPassword("password", matchesHash: hash!)Security Best Practices
1. Salt Generation
Always generate a new salt for each password. Never reuse salts.
// ✅ Good - new salt for each password
let salt = try BCryptSwiftModern.generateSalt()
let hash = try BCryptSwiftModern.hashPassword(password, withSalt: salt)
// ❌ Bad - reusing salt
let commonSalt = "$2a$10$vI8aWBnW3fID.ZQ4/zo1G." // Don't do this!2. Round Selection
Choose rounds based on your security requirements:
- Testing: 4 rounds (minimum, only for tests)
- Default: 10 rounds (good for most applications)
- High Security: 12+ rounds (for sensitive data)
- Maximum: 31 rounds (extremely slow)
// Measure performance on your target hardware
let start = Date()
let salt = try BCryptSwiftModern.generateSalt(config: BCryptConfiguration(rounds: 12))
let hash = try BCryptSwiftModern.hashPassword("test", withSalt: salt)
print("Time taken: \(Date().timeIntervalSince(start)) seconds")3. Password Length Limits
BCrypt has a maximum effective password length of 72 bytes:
// Passwords longer than 72 bytes will be truncated
let longPassword = String(repeating: "a", count: 100)
// Only the first 72 bytes are usedConsider pre-hashing very long passwords with SHA-256 if needed:
import CryptoKit
func hashLongPassword(_ password: String) throws -> String {
let passwordData = Data(password.utf8)
// Pre-hash if longer than 72 bytes
let effectivePassword: String
if passwordData.count > 72 {
let hashed = SHA256.hash(data: passwordData)
effectivePassword = hashed.compactMap { String(format: "%02x", $0) }.joined()
} else {
effectivePassword = password
}
let salt = try BCryptSwiftModern.generateSalt()
return try BCryptSwiftModern.hashPassword(effectivePassword, withSalt: salt)
}4. Error Handling
Always handle errors appropriately:
do {
let hash = try BCryptSwiftModern.hashPassword(password, withSalt: salt)
// Store hash in database
} catch BCryptError.invalidSalt(let details) {
print("Invalid salt: \(details)")
} catch BCryptError.passwordTooLong {
print("Password exceeds maximum length")
} catch BCryptError.randomGenerationFailed {
print("Failed to generate secure random data")
} catch {
print("Unexpected error: \(error)")
}5. Secure Storage
- Never store passwords in plain text
- Store only the bcrypt hash in your database
- The hash includes the salt, version, and rounds - store the entire string
// The hash contains everything needed for verification
let hash = "$2a$10$N9qo8uLOickgx2ZMRZoMye1ISwRJW4cG6j4fJdGGrFXL3IM5IZkta"
// Format: $version$rounds$salt+hashMigration Guide
From the Original BCryptSwift
If you're migrating from the original BCryptSwift by Felipe Florencio Garcia:
For Swift Package Manager users:
// Simply update your Package.swift URL:
.package(url: "https://github.com/wisetail/BCryptSwift.git", from: "2.0.1")
// The import statement remains the same:
import BCryptSwiftFor CocoaPods users:
# Replace this:
pod 'BCryptSwift'
# With this:
pod 'WisetailBCryptSwift'The API remains 100% compatible, so no code changes are needed.
From Version 1.x to 2.0
The library maintains backward compatibility while offering a modern API:
// Old way (still works)
let salt = BCryptSwift.generateSalt()
if let hash = BCryptSwift.hashPassword("password", withSalt: salt) {
// Use hash
}
// New way (recommended)
do {
let salt = try BCryptSwiftModern.generateSalt()
let hash = try BCryptSwiftModern.hashPassword("password", withSalt: salt)
// Use hash
} catch {
// Handle error
}From Other BCrypt Libraries
BCryptSwift is compatible with bcrypt hashes from other libraries:
// Hash from another bcrypt implementation
let phpHash = "$2y$10$vI8aWBnW3fID.ZQ4/zo1G.lmqOhE8jGGNsRIZH1ULHdxTVYmZlAeW"
// Verify with BCryptSwift
let isValid = try BCryptSwiftModern.verifyPassword("password", matchesHash: phpHash)Performance Considerations
BCrypt is intentionally slow to resist brute-force attacks. Performance varies by hardware:
| Rounds | iPhone 13 Pro | MacBook Pro M1 | Purpose | |--------|--------------|----------------|---------| | 4 | ~2 ms | ~1 ms | Testing only | | 10 | ~60 ms | ~30 ms | Default | | 12 | ~240 ms | ~120 ms | High security | | 14 | ~950 ms | ~475 ms | Very high security |
Choose rounds based on your security requirements and acceptable login time.
Requirements
- Swift 5.0+
- iOS 12.0+ / macOS 10.13+ / tvOS 12.0+ / watchOS 5.0+
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Please ensure:
- All tests pass
- New features include tests
- Security-related changes are thoroughly reviewed
- Documentation is updated
Security Disclosure
If you discover a security vulnerability, please create an issue.
License
BCryptSwift is available under the Apache License 2.0. See the LICENSE file for more info.
Credits
- Original BCryptSwift (this fork is based on): Felipe Florencio Garcia (BCryptSwift)
- Initial Swift port: Joe Kramer (JKBCrypt)
- Objective-C implementation: Jay Fuerstenberg (JFCommon)
- Original Java bcrypt: Damien Miller (jBCrypt)
Changelog
Version 2.0.0
- Security: Replaced
arc4random()withSecRandomCopyBytes - Memory Safety: Added secure memory cleanup
- Modern Swift: Proper error handling with
Resulttype - Async/Await: Added async methods for iOS 13+
- SPM Support: Added Swift Package Manager support
- Testing: Comprehensive test suite with security tests
- Documentation: Improved docs with security guidelines
- Compatibility: Maintained backward compatibility
Package Metadata
Repository: wisetail/bcryptswift
Default branch: master
README: README.md