Cryptographic algorithms
Passwork uses different cryptographic algorithms on the server and client.
Algorithms overview
| Purpose | Algorithm | Where used |
|---|---|---|
| Key derivation from password | PBKDF2 | Master key generation |
| Symmetric encryption (server) | AES-256-CFB | Server-side database encryption |
| Symmetric encryption (client) | CryptoJS AES-256-CBC | All client data |
| Asymmetric encryption | RSA-OAEP | Key exchange |
| Hashing | SHA-256, SHA-512 | Password verification, integrity |
| Random number generation | WebCrypto API | Key generation, IV, salt |
PBKDF2 (Password-Based Key Derivation Function 2)
PBKDF2 is used to derive a cryptographic key from the user's master password.
Client parameters
| Parameter | Value |
|---|---|
| Algorithm | PBKDF2 |
| Hash function | SHA-256 |
| Iterations | 300,000 |
| Output key length | 512 bits |
| Salt | 20 characters, unique per user |
| Library | pbkdf2 (npm) |
Server parameters
| Parameter | Value |
|---|---|
| Algorithm | PBKDF2 |
| Hash function | SHA-512 |
| Iterations | 600,000 |
| Output key length | 512 bits |
| Library | PHP hash_pbkdf2() |
SHA-256 with 300,000 iterations is used on the client for balance between security and browser performance. More stringent parameters (SHA-512, 600,000 iterations) are applied on the server for authentication password hashing.
Salt
| Parameter | Value |
|---|---|
| Length | 20 characters |
| Alphabet | A-Z, a-z, 0-9, @, ! (64 characters) |
| Entropy | ~120 bits |
| Generation | Server, cryptographically secure |
| Storage | Server, in user profile |
| Uniqueness | Unique per user |
Salt is always generated on the server and transmitted to the client. The client does not generate salt independently, ensuring use of a cryptographically secure generator.
Result format
PBKDF2 result on the client is encoded as a string:
pbkdf:sha256:300000:64:{salt}
Purpose of high iteration count
High iteration count (300,000+) significantly slows brute-force attacks:
- Each attempt takes substantial time
- Parallel GPU brute-forcing is hindered
- Precomputed tables are impossible due to unique salt
Automatic hash migration
The system supports automatic migration of PBKDF2 hashes when parameters change.
Server hash format:
pbkdf:{base64_hash}:{algorithm}:{iterations}:{length}:{salt}
Example: pbkdf:Abc123...==:sha512:600000:64:xY3zKmN9qR2w...
Migration mechanism:
- User logs into the system
- System checks existing hash parameters
- If parameters differ from current settings → rehashing required
- Password is automatically hashed with new parameters
- New hash is saved to database
What can be updated:
| Parameter | Current value | Example new values |
|---|---|---|
| Hash algorithm | SHA-512 | SHA3-512, BLAKE2b512 |
| Iteration count | 600,000 | 1,000,000, 2,000,000 |
| Key length | 512 bits | 256, 1024 bits |
When parameters are updated:
- New users → immediately get hashes with new parameters
- Existing users → migration occurs on next login
- Users without login → old hashes continue to work
Migration is transparent to users — they just enter their password as usual.
AES (Advanced Encryption Standard)
Passwork uses different AES modes on server and client.
Server encryption: AES-256-CFB
AES-256 in CFB (Cipher Feedback) mode via OpenSSL is used on the server.
| Parameter | Value |
|---|---|
| Algorithm | AES-256-CFB |
| Key length | 256 bits |
| Block size | 128 bits |
| Initialization Vector (IV) | 128 bits, random per encryption |
| Library | OpenSSL (via PHP) |
CFB mode characteristics:
- Stream cipher mode
- No padding required
- IV stored with ciphertext
Usage: encrypting data in database (external links, settings, LDAP/SMTP passwords).
Server IV generation:
| Parameter | Value |
|---|---|
| Length | 128 bits |
| Generator | Cryptographically secure (OS CSPRNG) |
| Uniqueness | New IV for each encryption operation |
| Storage | At the beginning of encrypted data |
Encrypted data structure: [128 bit IV][ciphertext]
Client encryption: CryptoJS AES-256-CBC
CryptoJS library is used on the client for all symmetric encryption operations.
| Parameter | Value |
|---|---|
| Library | CryptoJS |
| Mode | CBC (Cipher Block Chaining) |
| Key length | 256 bits |
| Padding | PKCS#7 |
| IV | Automatic (128 bits) |
| KDF | Built-in key derivation function |
| Output encoding | Base32 |
Key transformation to AES-256:
Symmetric keys in Passwork are 100 characters (~596 bits entropy), but AES-256 requires exactly 256-bit key. CryptoJS automatically performs transformation via key derivation function (KDF):
- Random 64-bit salt is generated
- Original 100-character key is transformed to 256-bit AES key
- Salt is saved with ciphertext for decryption
Input 596 bits guarantee maximum strength of the 256-bit output key. This also ensures unification — all symmetric keys (vault, record, attachment) are generated identically.
Client IV generation:
CryptoJS automatically generates IV for each encryption:
| Parameter | Value |
|---|---|
| Length | 128 bits |
| Generator | window.crypto.getRandomValues() |
| Uniqueness | New IV for each encryption operation |
| Storage format | Included in OpenSSL-compatible format |
CryptoJS output format: "Salted__" + [64 bit salt] + [ciphertext with IV] → Base64
Why different modes?
| Criterion | Server (CFB) | Client (CBC) |
|---|---|---|
| Library | OpenSSL | CryptoJS |
| Choice reason | PHP standard | Backward compatibility |
| Advantages | Stream mode | Wide support |
CryptoJS vs WebCrypto API
CryptoJS library is used for symmetric encryption on the client, not the native WebCrypto API. Here's a comparison.
Comparison:
| Aspect | CryptoJS | WebCrypto API |
|---|---|---|
| Implementation | Pure JavaScript | Native browser code |
| Performance | Slower | Significantly faster |
| Timing attack protection | No guarantees | Implementation-level protection |
| Memory management | Keys in JS heap | Keys can be non-extractable |
| API | Synchronous | Asynchronous (Promise) |
| Compatibility | Any JS environment | Modern browsers only |
Why CryptoJS is used:
- Backward compatibility — existing encrypted data uses CryptoJS format
- Synchronous API — easier integration with existing codebase
- Universality — works in any JavaScript environment
Security measures in current implementation:
- PBKDF2 via separate library — master key derived via cryptographically strong PBKDF2
- WebCrypto for RSA — critical asymmetric operations performed via native API
- Cryptographically secure IV generation — via
crypto.getRandomValues(), notMath.random() - Data encrypted once — timing attacks require multiple operations with same key
Risk assessment:
| Threat | Risk level | Justification |
|---|---|---|
| Timing attacks | Low | Requires local browser access |
| Memory exposure | Low | With such access, easier to intercept password during input |
| Known CVE | Minimal | Used CryptoJS version has no critical vulnerabilities |
Future versions plan gradual transition to WebCrypto API for symmetric encryption. This will enable:
- Using AES-GCM (authenticated encryption) instead of AES-CBC
- 10-100x performance improvement
- Browser-level key protection
Migration will be performed with backward compatibility for existing data.
RSA (Rivest–Shamir–Adleman)
RSA is used for asymmetric encryption — secure exchange of symmetric keys between users.
Parameters
| Parameter | Value |
|---|---|
| Algorithm | RSA-OAEP |
| Modulus length | 2048 bits |
| Public exponent | 65537 (0x010001) |
| Hash function | SHA-256 |
| Library | WebCrypto API (crypto.subtle) |
Key generation
RSA keys are generated on the client using WebCrypto API:
crypto.subtle.generateKey({
name: 'RSA-OAEP',
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: 'SHA-256'
}, true, ['encrypt', 'decrypt'])
Storage formats
| Key | Format | Description |
|---|---|---|
| Public | SPKI (PEM) | SubjectPublicKeyInfo |
| Private | PKCS#8 (PEM) | Encrypted with master key |
Supported formats
The system supports key import in formats:
- PKCS#1 —
-----BEGIN RSA PRIVATE KEY----- - PKCS#8 —
-----BEGIN PRIVATE KEY----- - SPKI —
-----BEGIN PUBLIC KEY----- - JWK — JSON Web Key (for conversion)
Backward compatibility
For legacy 1024-bit keys, JSEncrypt library fallback is used, as WebCrypto API doesn't support RSA-1024.
Why RSA-2048?
| Aspect | Description |
|---|---|
| Security | Equivalent to 112 bits of symmetric encryption |
| Performance | Acceptable speed in browser |
| Compatibility | Wide support across all browsers |
Hashing
SHA-256
| Parameter | Value |
|---|---|
| Hash length | 256 bits |
| Library (client) | js-sha256 |
| Usage | Master key hash, PBKDF2, RSA-OAEP |
SHA-512
| Parameter | Value |
|---|---|
| Hash length | 512 bits |
| Library (client) | js-sha512 |
| Library (server) | PHP hash() |
| Usage | Server PBKDF2, server key hash, search indices |
Random number generation
All cryptographic parameters are generated using cryptographically secure random number generators (CSPRNG).
Entropy sources
Server (PHP)
| Generator | Entropy source | Usage |
|---|---|---|
random_bytes() | OS CSPRNG | Binary data (keys, tokens) |
random_int() | OS CSPRNG | Character strings (salts, codes) |
OS entropy sources:
- Linux:
/dev/urandom,getrandom()syscall - Windows:
CryptGenRandom()(CryptoAPI) - macOS:
arc4random_buf()
Client (JavaScript)
| Generator | Entropy source | Usage |
|---|---|---|
window.crypto.getRandomValues() | Browser/OS CSPRNG | Numbers, arrays |
CryptoJS.lib.WordArray.random() | crypto.getRandomValues() | IV for AES |
Browser entropy sources:
- Chromium/Electron: BoringSSL CSPRNG
- Firefox: NSS (Network Security Services)
- Safari: CommonCrypto (Apple)
Cryptographic strength guarantees
| Property | Guarantee |
|---|---|
| Unpredictability | Impossible to predict next value |
| Uniform distribution | All values equally probable |
| Unbiased | Rejection sampling used |
| Availability | Generators don't block |
Generated keys and tokens
| What is generated | Length | Alphabet | Entropy |
|---|---|---|---|
| Symmetric keys | |||
| Vault key | 100 characters | A-Z, a-z, 0-9, @, ! | ~596 bits |
| Record key | 100 characters | A-Z, a-z, 0-9, @, ! | ~596 bits |
| Attachment key | 100 characters | A-Z, a-z, 0-9, @, ! | ~596 bits |
| Link key | 100 characters → 256 bits | A-Z, a-z, 0-9, @, ! | ~596 bits input |
| Server encryption key | 256 bits | Binary | 256 bits |
| Salts | |||
| PBKDF2 salt | 20 characters | A-Z, a-z, 0-9, @, ! | ~120 bits |
| Vault salt | 32 characters | A-Z, a-z, 0-9, @, ! | ~190 bits |
| Tokens and secrets | |||
| Secret Code (localStorage) | 100 characters | A-Z, a-z, 0-9 | ~596 bits |
| Extension Secret | 60 characters | A-Z, a-z, 0-9 | ~357 bits |
| Auth Token (extension) | 400 bits | Hex | 400 bits |
| External link token | 43 characters | A-Z, a-z, 0-9 | ~256 bits |
| Session tokens | |||
| Access Token | 256 bits | Base64 | 256 bits |
| Refresh Token | 256 bits | Base64 | 256 bits |
| Initialization vectors | |||
| IV (server, AES-CFB) | 128 bits | Binary | 128 bits |
| IV (client, AES-CBC) | 128 bits | Binary | 128 bits |
Token uniqueness verification
For tokens requiring guaranteed uniqueness (link tokens, invite codes), database verification is performed:
- Random token is generated
- Uniqueness is checked in database
- On collision — regenerate
- Guaranteed unique token is returned
Collision probability:
- 43 characters from 62: ~10^77 combinations
- With billion tokens: collision probability ≈ 10^-59
- Practically impossible
Cryptographic libraries
Client (JavaScript/TypeScript)
| Library | Purpose |
|---|---|
| WebCrypto API | RSA operations, random numbers |
| CryptoJS | AES encryption (all data) |
| js-sha256 | SHA-256 hashing |
| js-sha512 | SHA-512 hashing |
| pbkdf2 | PBKDF2 key derivation |
| node-jsencrypt | RSA fallback (1024-bit) |
| pem-jwk | PEM ↔ JWK conversion |
| totp-generator | TOTP code generation |
Server (PHP)
| Component | Purpose |
|---|---|
| OpenSSL | AES-256-CFB encryption |
| hash() | SHA-256, SHA-512 hashing |
| hash_pbkdf2() | PBKDF2 key derivation |
| random_bytes() | Random data generation |
Comparison with industry standards
| Parameter | Passwork | NIST recommendations (2024) | Status |
|---|---|---|---|
| Symmetric encryption | AES-256 | AES-128/192/256 | ✓ |
| Asymmetric encryption | RSA-2048 | RSA-2048+ | ✓ |
| Hashing | SHA-256/512 | SHA-2 family | ✓ |
| PBKDF2 iterations | 300K/600K | ≥310K (SHA-256) | ✓ |
| Random number generator | CSPRNG | CSPRNG | ✓ |