[English] | [Italiano]
A zero-knowledge desktop password manager built with enterprise-grade architecture, implementing Software Engineering best practices through layered design, security patterns, and comprehensive testing.
- Overview
- Key Features
- Architecture
- Technology Stack
- Project Structure
- Design Patterns
- Security Architecture
- Use Cases
- UML Diagrams
- Installation
- Configuration
- Usage
- Testing
- API Documentation
- Contributing
- License
- Acknowledgments
SafeCore is a desktop application for secure password management designed with a layered architecture that separates concerns across UI, business logic, security, and persistence layers. The application implements zero-knowledge architecture where no sensitive data is stored in plain text, ensuring maximum security for user credentials.
The project demonstrates professional software engineering practices including:
- Clean Architecture with clear separation of concerns
- Design Patterns (Strategy, Repository, Builder, Observer, Singleton, Factory, Dependency Injection)
- Comprehensive unit and integration testing
- Security-first design with AES-256-CBC encryption
- Modern JavaFX Material Design UI
- Spring Boot dependency injection and transaction management
- Zero-Knowledge Architecture: All sensitive data is encrypted before storage
- AES-256-CBC Encryption: Industry-standard encryption with unique initialization vectors
- BCrypt Password Hashing: User passwords hashed with work factor 12
- Password Strength Evaluation: Real-time password strength analysis with scoring system
- Security Audit System: Comprehensive vault analysis identifying weak, old, and reused passwords
- Secure Password Generator: Cryptographically secure random password generation with customizable complexity
- Vault Management: Complete CRUD operations for password entries with encrypted storage
- User Authentication: Secure registration and login with password strength validation
- Account Management: Secure account deletion with data sanitization and confirmation phrase
- Password Expiration: Optional password expiration dates with automatic cleanup
- Search and Filter: Real-time search across service names and usernames
- Backup and Restore: Encrypted vault export/import with .safe file format
- SafeSend: Secure one-time secret sharing with time-limited expiration
- Modern Material Design UI: Clean and intuitive JavaFX interface
- Session Management: Centralized user session with automatic logout
- Password Visibility Toggle: Show/hide password functionality
- Real-time Feedback: Password strength indicators and validation hints
- Health Score Dashboard: Visual security score based on vault audit results
- Responsive Design: Adaptive layout with overlay dialogs and animations
┌─────────────────────────────────────────────────────────────┐
│ PRESENTATION LAYER │
│ (JavaFX UI) │
│ - Controllers (Login, Dashboard, Register, Audit) │
│ - Navigation System (SceneNavigator) │
│ - Session Management (SessionContext) │
│ - Global Exception Handler │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ BUSINESS LAYER │
│ (Service Interfaces) │
│ - VaultService (entry management + encryption) │
│ - UserService (registration + authentication) │
│ - SecurityAuditService (vault analysis) │
│ - SafeSendService (secure sharing) │
│ - BackupService (export/import) │
│ - PasswordHintService (strength validation) │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ SECURITY LAYER │
│ (Encryption & Hashing) │
│ - EncryptionStrategy (interface) │
│ - AESEncryptionStrategy (AES-256-CBC implementation) │
│ - PasswordHasher (BCrypt with salt) │
│ - PasswordGenerator (secure random generation) │
│ - PasswordStrengthEvaluator (strength scoring) │
│ - KeyManager (encryption key management) │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ PERSISTENCE LAYER │
│ (JPA Repositories) │
│ - UserRepository (user CRUD) │
│ - PasswordEntryRepository (entry CRUD) │
│ - SafeSendRepository (temporary secret storage) │
│ - PostgreSQL Database (cloud-hosted relational DB) │
└─────────────────────────────────────────────────────────────┘
| Layer | Component | Responsibilities |
|---|---|---|
| UI | LoginController |
User authentication UI and form validation |
RegisterController |
User registration with real-time password strength feedback | |
DashboardController |
Main vault management, entry CRUD, health score display | |
AuditController |
Security audit visualization and recommendations | |
SceneNavigator |
Centralized view navigation with Spring context injection | |
SessionContext |
Singleton session management with user state tracking | |
| Business | VaultService |
Entry encryption/decryption, CRUD operations, backup/restore |
UserService |
User registration, login validation, password strength checking | |
SecurityAuditService |
Vault analysis, weakness detection, health score calculation | |
SafeSendService |
Temporary secret creation, one-time access management | |
| Security | AESEncryptionStrategy |
AES-256-CBC encryption with random IVs |
PasswordHasher |
BCrypt hashing with configurable work factor | |
PasswordGenerator |
Cryptographically secure password generation | |
PasswordStrengthEvaluator |
Multi-factor password strength analysis | |
| Persistence | UserRepository |
User entity persistence and query operations |
PasswordEntryRepository |
Encrypted password entry storage | |
PasswordEntryEntity |
JPA entity with Many-to-One relationship to User | |
UserEntity |
JPA entity with unique email constraint |
- Java 17: Modern Java LTS version with records, sealed classes, and enhanced pattern matching
- Spring Boot 3.2.2: Enterprise framework for dependency injection, transaction management, and data access
- JavaFX 17: Modern desktop UI framework with FXML declarative layouts
- PostgreSQL: Production-grade relational database with cloud hosting (Neon)
- JBCrypt 0.4: BCrypt password hashing implementation
- Java Cryptography Extension (JCE): AES-256 encryption support
- JUnit 5: Modern testing framework with parameterized and nested tests
- Mockito: Mocking framework for unit testing
- Spring Boot Test: Integration testing support with transaction rollback
- TestFX: JavaFX UI testing framework
- JaCoCo: Code coverage analysis tool
- Maven: Project management and dependency resolution
- Spring Boot Maven Plugin: Executable JAR packaging
SafeCoreProject/
├── src/
│ ├── main/
│ │ ├── java/com/safecore/
│ │ │ ├── SafeCoreApplication.java # Main application entry point
│ │ │ ├── business/ # Business logic layer
│ │ │ │ ├── domain/ # Domain models (immutable)
│ │ │ │ │ ├── User.java
│ │ │ │ │ ├── UserBuilder.java
│ │ │ │ │ ├── PasswordEntry.java
│ │ │ │ │ ├── UserFactory.java
│ │ │ │ │ └── AuditResult.java
│ │ │ │ ├── exception/ # Custom exceptions
│ │ │ │ │ ├── SafeCoreException.java
│ │ │ │ │ ├── UserAlreadyExistsException.java
│ │ │ │ │ ├── UserNotFoundException.java
│ │ │ │ │ ├── WeakPasswordException.java
│ │ │ │ │ ├── InvalidTokenException.java
│ │ │ │ │ ├── ExpiredLinkException.java
│ │ │ │ │ └── LinkNotFoundException.java
│ │ │ │ ├── hints/ # Password validation rules
│ │ │ │ │ ├── HintLevel.java
│ │ │ │ │ ├── PasswordHint.java
│ │ │ │ │ └── rules/
│ │ │ │ │ ├── PasswordRule.java
│ │ │ │ │ ├── MinLengthRule.java
│ │ │ │ │ └── ComplexityRule.java
│ │ │ │ └── service/ # Service interfaces & implementations
│ │ │ │ ├── UserService.java
│ │ │ │ ├── VaultService.java
│ │ │ │ ├── SecurityAuditService.java
│ │ │ │ ├── SafeSendService.java
│ │ │ │ ├── BackupService.java
│ │ │ │ ├── PasswordHintService.java
│ │ │ │ ├── VaultObserver.java
│ │ │ │ └── impl/ # Service implementations
│ │ │ │ ├── UserServiceImpl.java
│ │ │ │ ├── PasswordServiceImpl.java
│ │ │ │ ├── SecurityAuditServiceImpl.java
│ │ │ │ ├── SafeSendServiceImpl.java
│ │ │ │ └── BackupServiceImpl.java
│ │ │ ├── persistence/ # Data access layer
│ │ │ │ ├── entity/ # JPA entities
│ │ │ │ │ ├── UserEntity.java
│ │ │ │ │ ├── PasswordEntryEntity.java
│ │ │ │ │ └── SafeSendEntryEntity.java
│ │ │ │ └── repository/ # Spring Data repositories
│ │ │ │ ├── UserRepository.java
│ │ │ │ ├── PasswordEntryRepository.java
│ │ │ │ └── SafeSendRepository.java
│ │ │ ├── security/ # Security layer
│ │ │ │ ├── EncryptionStrategy.java # Strategy pattern interface
│ │ │ │ ├── AESEncryptionStrategy.java
│ │ │ │ ├── EncryptionFactory.java
│ │ │ │ ├── KeyManager.java
│ │ │ │ ├── PasswordHasher.java
│ │ │ │ ├── PasswordGenerator.java
│ │ │ │ └── PasswordStrengthEvaluator.java
│ │ │ └── ui/ # Presentation layer
│ │ │ ├── AppLauncher.java # JavaFX launcher
│ │ │ ├── GlobalExceptionHandler.java
│ │ │ ├── controller/ # UI controllers
│ │ │ │ ├── LoginController.java
│ │ │ │ ├── RegisterController.java
│ │ │ │ ├── DashboardController.java
│ │ │ │ ├── AuditController.java
│ │ │ │ ├── SafeSendController.java
│ │ │ │ ├── AddEntryController.java
│ │ │ │ ├── SettingsController.java
│ │ │ │ └── BackupController.java
│ │ │ ├── navigation/
│ │ │ │ └── SceneNavigator.java
│ │ │ └── session/
│ │ │ └── SessionContext.java
│ │ └── resources/
│ │ ├── application.properties # Spring configuration
│ │ ├── style.css # Application stylesheet
│ │ └── com/safecore/ui/view/ # FXML views
│ │ ├── login.fxml
│ │ ├── register.fxml
│ │ ├── dashboard.fxml
│ │ ├── audit-view.fxml
│ │ ├── safesend-view.fxml
│ │ └── settings-view.fxml
│ └── test/
│ ├── java/com/safecore/ # Test suite
│ │ ├── SafeCoreIntegrationTest.java
│ │ ├── business/service/
│ │ │ ├── UserServiceTest.java
│ │ │ ├── PasswordServiceTest.java
│ │ │ ├── SecurityAuditServiceTest.java
│ │ │ ├── BackupIntegrationTest.java
│ │ │ └── SafeSendServiceTest.java
│ │ ├── business/exception/
│ │ │ ├── UserNotFoundExceptionTest.java
│ │ │ ├── UserAlreadyExistsExceptionTest.java
│ │ │ ├── InvalidTokenExceptionTest.java
│ │ │ ├── ExpiredLinkExceptionTest.java
│ │ │ ├── LinkNotFoundExceptionTest.java
│ │ │ ├── SafeCoreExceptionTest.java
│ │ │ └── WeakPasswordExceptionTest.java
│ │ ├── business/hints/
│ │ │ └── PasswordHintServiceTest.java
│ │ ├── security/
│ │ │ ├── AESEncryptionStrategyTest.java
│ │ │ ├── AESEncryptionPerformanceTest.java
│ │ │ ├── PasswordGeneratorTest.java
│ │ │ ├── PasswordStrengthEvaluatorTest.java
│ │ │ ├── PasswordHasherTest.java
│ │ │ └── PasswordHasherPerformanceTest.java
│ │ ├── persistence/repository/
│ │ │ ├── SafeSendRepositoryTest.java
│ │ │ └── UserRepositoryTest.java
│ │ └── ui/
│ │ ├── TestFXBaseTest.java
│ │ └── controller/
│ │ ├── LoginControllerTest.java
│ │ ├── RegisterControllerTest.java
│ │ ├── SafeSendControllerTest.java
│ │ └── SettingsControllerTest.java
│ └── resources
│ └── application-test.properties
├── docs/
│ └── uml/
│ ├── class-diagram.puml # Complete system class diagram
│ ├── class-diagram-part1-ui-navigation.puml
│ ├── class-diagram-part2-business-logic.puml
│ ├── class-diagram-part3-persistence.puml
│ ├── class-diagram-part4-security.puml
│ ├── component-diagram.puml
│ ├── package-diagram.puml
│ ├── sequence-login.puml
│ ├── sequence-register.puml
│ ├── sequence-save-entry.puml
│ ├── sequence-audit.puml
│ └── use-case.puml
├── pom.xml # Maven configuration
├── README.md # This file
└── README.it.md # Italian documentation
The Strategy pattern allows switching between different encryption algorithms without modifying client code.
public interface EncryptionStrategy {
byte[] encrypt(String plaintext);
String decrypt(byte[] ciphertext);
}
@Component
public class AESEncryptionStrategy implements EncryptionStrategy {
private final KeyManager keyManager;
@Override
public byte[] encrypt(String plainText) {
// AES-256-CBC implementation with random IV
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, keyManager.getSecretKey(), ivSpec);
byte[] encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
// Prepend IV to ciphertext for decryption
byte[] result = new byte[iv.length + encrypted.length];
System.arraycopy(iv, 0, result, 0, iv.length);
System.arraycopy(encrypted, 0, result, iv.length, encrypted.length);
return result;
}
@Override
public String decrypt(byte[] cipherText) {
// Extract IV and decrypt
byte[] iv = new byte[16];
byte[] encrypted = new byte[cipherText.length - 16];
System.arraycopy(cipherText, 0, iv, 0, 16);
System.arraycopy(cipherText, 16, encrypted, 0, encrypted.length);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keyManager.getSecretKey(), new IvParameterSpec(iv));
return new String(cipher.doFinal(encrypted), StandardCharsets.UTF_8);
}
}Benefits:
- Easy to add new encryption algorithms (RSA, ChaCha20, etc.)
- Client code depends on interface, not implementation
- Encryption logic encapsulated and testable in isolation
The Repository pattern abstracts data access logic, allowing database changes without impacting business logic.
@Repository
public interface PasswordEntryRepository extends JpaRepository<PasswordEntryEntity, UUID> {
List<PasswordEntryEntity> findByUserEmail(String email);
List<PasswordEntryEntity> findByUser(UserEntity user);
void deleteByExpiresAtBefore(LocalDateTime now);
}Benefits:
- Database implementation can change without affecting services
- Centralized query logic with Spring Data JPA method naming
- Easy to mock for unit testing
- Built-in transaction management
The Builder pattern enables fluent and readable construction of complex immutable objects.
public final class PasswordEntry {
private final UUID id;
private final String serviceName;
private final String username;
private final byte[] encryptedPassword;
private final LocalDateTime createdAt;
private PasswordEntry(Builder builder) {
this.id = builder.id;
this.serviceName = builder.serviceName;
this.username = builder.username;
this.encryptedPassword = builder.encryptedPassword != null
? builder.encryptedPassword.clone() : null;
this.createdAt = builder.createdAt;
}
public static class Builder {
private UUID id;
private String serviceName;
private String username;
private byte[] encryptedPassword;
private LocalDateTime createdAt;
public Builder id(UUID id) {
this.id = id;
return this;
}
public Builder serviceName(String sn) {
this.serviceName = sn;
return this;
}
public Builder username(String un) {
this.username = un;
return this;
}
public Builder encryptedPassword(byte[] ep) {
this.encryptedPassword = ep;
return this;
}
public Builder createdAt(LocalDateTime ca) {
this.createdAt = ca;
return this;
}
public PasswordEntry build() {
Objects.requireNonNull(serviceName, "Service name required");
Objects.requireNonNull(username, "Username required");
Objects.requireNonNull(encryptedPassword, "Encrypted password required");
if (createdAt == null) this.createdAt = LocalDateTime.now();
return new PasswordEntry(this);
}
}
}
// Usage
PasswordEntry entry = new PasswordEntry.Builder()
.serviceName("Gmail")
.username("user@example.com")
.encryptedPassword(encryptedBytes)
.createdAt(LocalDateTime.now())
.build();Benefits:
- Immutable objects ensure thread safety
- Fluent API improves code readability
- Compile-time validation of required fields
- Flexible parameter combinations
The Observer pattern enables reactive UI updates when vault data changes.
public interface VaultObserver {
void onVaultChanged();
}
@Service
public class VaultService {
private final List<VaultObserver> observers = new ArrayList<>();
public void addObserver(VaultObserver observer) {
observers.add(observer);
}
public void notifyObservers() {
observers.forEach(VaultObserver::onVaultChanged);
}
@Transactional
public void addEntry(String service, String username, String plain) {
// ... save entry logic ...
notifyObservers(); // Notify all observers
}
}
@Component
public class DashboardController implements VaultObserver {
@Override
public void onVaultChanged() {
refreshVault(); // Update UI table
updateHealthScore(); // Recalculate security score
}
}Benefits:
- Decoupled communication between service and UI
- Multiple observers can react to same event
- Easy to add new observers without modifying VaultService
The Singleton pattern ensures a single instance for application-wide user session.
public final class SessionContext {
private static volatile String loggedUserEmail;
private static LocalDateTime loginTime;
private SessionContext() {
// Private constructor prevents instantiation
}
public static void login(String email) {
if (email == null || email.isBlank()) {
throw new IllegalArgumentException("Invalid session email");
}
loggedUserEmail = email;
loginTime = LocalDateTime.now();
}
public static void logout() {
loggedUserEmail = null;
loginTime = null;
}
public static boolean isLoggedIn() {
return loggedUserEmail != null;
}
public static String getCurrentUserEmail() {
if (!isLoggedIn()) {
throw new IllegalStateException("No user logged in");
}
return loggedUserEmail;
}
}Benefits:
- Global access point for session state
- Thread-safe with volatile keyword
- Prevents multiple session instances
The Factory pattern centralizes creation logic for encryption strategies.
@Component
public class EncryptionFactory {
private final Map<String, EncryptionStrategy> strategies;
public EncryptionFactory(List<EncryptionStrategy> strategyList) {
this.strategies = strategyList.stream()
.collect(Collectors.toMap(
s -> s.getClass().getSimpleName()
.replace("EncryptionStrategy", "")
.toUpperCase(),
Function.identity()
));
}
public EncryptionStrategy getStrategy(String type) {
EncryptionStrategy strategy = strategies.get(type.toUpperCase());
if (strategy == null) {
throw new IllegalArgumentException("Unsupported encryption: " + type);
}
return strategy;
}
public EncryptionStrategy getDefaultStrategy() {
return getStrategy("AES");
}
}Benefits:
- Centralized strategy creation and configuration
- Easy to add new encryption algorithms via Spring
- Runtime strategy selection based on requirements
Spring's DI container manages object lifecycles and dependencies.
@Service
public class VaultService {
private final PasswordEntryRepository repository;
private final UserRepository userRepository;
private final EncryptionStrategy encryptionStrategy;
// Constructor injection (recommended)
public VaultService(PasswordEntryRepository repository,
UserRepository userRepository,
EncryptionFactory encryptionFactory) {
this.repository = repository;
this.userRepository = userRepository;
this.encryptionStrategy = encryptionFactory.getDefaultStrategy();
}
}Benefits:
- Loose coupling between components
- Easy to mock dependencies for unit testing
- Centralized configuration and lifecycle management
- Promotes interface-based programming
Plaintext Password: "MySecretPass123!"
↓
[Key Manager]
SecureRandom generates 256-bit key
↓
[AES Cipher Init]
Generate random 128-bit IV
Mode: CBC (Cipher Block Chaining)
Padding: PKCS5
↓
[Encryption]
Encrypt plaintext using key + IV
↓
[Storage Format]
[IV (16 bytes)][Encrypted Data (variable)]
↓
Store in database as byte[]
Security Properties:
- Each password has unique IV (prevents pattern detection)
- 256-bit key provides 2^256 possible keys
- CBC mode ensures identical plaintexts produce different ciphertexts
- Key stored in memory only, never persisted
User Password: "MyMasterPass!"
↓
[BCrypt Algorithm]
Work Factor: 12 (2^12 = 4096 iterations)
Salt: 128-bit random value (auto-generated)
↓
[Hashing Process]
Blowfish cipher + salt + iterations
↓
[Result]
$2a$12$[22-char salt][31-char hash]
↓
Store in UserEntity.passwordHash
Security Properties:
- Adaptive hashing (work factor increases with hardware)
- Unique salt per password prevents rainbow table attacks
- Computationally expensive (mitigates brute force)
- Industry-standard algorithm (OWASP recommended)
SafeCore implements true zero-knowledge security:
- Master Password Never Stored: User passwords are hashed with BCrypt, original value never persisted
- Vault Entries Encrypted: All passwords encrypted with AES-256 before database storage
- Unique IVs: Each encryption uses fresh random IV (no pattern leakage)
- Key in Memory: Encryption key generated at startup, never written to disk
- Session-Based Decryption: Passwords decrypted only when user is authenticated
The system evaluates password strength using multiple criteria:
@Component
public class PasswordStrengthEvaluator {
public Strength evaluate(String password) {
if (password == null || password.length() < 8) return Strength.WEAK;
boolean hasLower = password.matches(".*[a-z].*");
boolean hasUpper = password.matches(".*[A-Z].*");
boolean hasDigit = password.matches(".*\\d.*");
boolean hasSymbol = password.matches(".*[^a-zA-Z0-9].*");
int score = Stream.of(hasLower, hasUpper, hasDigit, hasSymbol)
.mapToInt(b -> b ? 1 : 0)
.sum();
if (score <= 2 || password.length() < 8) return Strength.WEAK;
if (score == 3) return Strength.MEDIUM;
return Strength.STRONG;
}
public enum Strength { WEAK, MEDIUM, STRONG }
}Criteria:
- Length: Minimum 8 characters (12+ recommended)
- Lowercase letters: [a-z]
- Uppercase letters: [A-Z]
- Digits: [0-9]
- Special characters: [!@#$%^&*()-_=+[]{}<>]
Scoring:
- WEAK: 0-2 criteria met OR < 8 characters
- MEDIUM: 3 criteria met
- STRONG: All 4 criteria met
The audit service analyzes vault security and calculates health score:
@Service
public class SecurityAuditServiceImpl implements SecurityAuditService {
@Override
public AuditResult runAudit() {
List<PasswordEntryEntity> entries = vaultService.getEntriesForCurrentUser();
// Decrypt all passwords for analysis
List<String> decryptedPasswords = entries.stream()
.map(e -> vaultService.decryptPassword(e.getEncryptedPassword()))
.toList();
// Analyze weaknesses
int weakCount = countWeakPasswords(decryptedPasswords);
int oldCount = countOldPasswords(entries);
int reusedCount = countReusedPasswords(decryptedPasswords);
// Calculate health score
int score = 100 - (weakCount * 10) - (oldCount * 5) - (reusedCount * 15);
return new AuditResult(Math.max(score, 0), weakCount, oldCount, reusedCount, entries.size());
}
}Audit Criteria:
- Weak Passwords: -10 points each (passwords not meeting strength requirements)
- Old Passwords: -5 points each (passwords older than 1 year)
- Reused Passwords: -15 points per reuse (same password used multiple times)
Health Score Ranges:
- 80-100: Excellent (green indicator)
- 50-79: Good (yellow indicator)
- 0-49: Poor (red indicator)
Primary Actor: New User
Preconditions:
- Application launched
- User not authenticated
Main Success Scenario:
- User clicks "Register" on login screen
- System displays registration form
- User enters email address
- User enters master password
- System validates password strength in real-time
- User confirms master password
- System validates:
- Email format is valid
- Email not already registered
- Passwords match
- Password meets minimum strength (MEDIUM or STRONG)
- System hashes password with BCrypt
- System creates UserEntity with hashed password
- System saves user to database
- System displays success message
- System redirects to login screen
Alternative Flows:
-
5a. Password is WEAK:
- System displays strength indicator in red
- System shows specific weakness hints
- User modifies password
- Return to step 5
-
7a. Email already registered:
- System displays "Account exists" error
- System suggests login instead
- Use case ends
-
7b. Passwords don't match:
- System highlights mismatch error
- User corrects password
- Return to step 7
Postconditions:
- New user account created with encrypted credentials
- User can log in with email and master password
Primary Actor: Registered User
Preconditions:
- User has registered account
- User not currently authenticated
Main Success Scenario:
- User enters email address
- User enters master password
- User clicks "Login" button
- System retrieves UserEntity by email
- System verifies password using BCrypt
- System initializes SessionContext with user email
- System records login timestamp
- System redirects to Dashboard
- System loads user's encrypted vault entries
- System displays vault table with decrypted data
Alternative Flows:
-
4a. Email not found:
- System displays "Invalid credentials" error
- System does not reveal whether email or password was wrong (security)
- Use case ends
-
5a. Password verification fails:
- System displays "Invalid credentials" error
- Use case ends
Postconditions:
- User authenticated with active session
- Dashboard loaded with user's vault data
- Security audit score calculated and displayed
Primary Actor: Authenticated User
Preconditions:
- User logged in
- Dashboard displayed
Main Success Scenario:
- User clicks "Add Entry" button
- System displays entry form overlay
- User enters:
- Service name (e.g., "Gmail")
- Username/email
- Password (manual or generated)
- Optional: Expiration date
- System validates all fields are non-empty
- System encrypts password using AES-256-CBC
- System generates random 128-bit IV
- System creates PasswordEntryEntity
- System associates entry with current user
- System saves entry to database
- System notifies VaultObservers
- System refreshes dashboard table
- System updates health score
- System displays success notification
Alternative Flows:
-
3a. User clicks "Generate Password":
- System shows password generator form
- User selects length (12-32 characters)
- System generates cryptographically secure random password
- System validates generated password meets STRONG criteria
- System displays generated password in form
- User can copy to clipboard or regenerate
- Return to step 3
-
4a. Required field empty:
- System highlights empty field
- System prevents form submission
- Return to step 3
Postconditions:
- New encrypted entry stored in database
- Entry visible in dashboard table
- Health score recalculated
- Entry decryptable only by authenticated user
Primary Actor: Authenticated User
Preconditions:
- User logged in
- User has at least one vault entry
Main Success Scenario:
- User clicks "Security Audit" button
- System retrieves all user's entries
- System decrypts all passwords for analysis
- System evaluates each password strength
- System identifies weak passwords (< STRONG)
- System identifies old passwords (> 1 year old)
- System identifies reused passwords (duplicates)
- System calculates health score:
- Base: 100 points
- Deduct 10 points per weak password
- Deduct 5 points per old password
- Deduct 15 points per reused password
- System displays audit results:
- Overall health score
- Count of weak passwords
- Count of old passwords
- Count of reused passwords
- System color-codes score (green/yellow/red)
- System provides recommendations
Alternative Flows:
- 2a. Vault is empty:
- System displays "No entries to audit"
- System shows perfect 100/100 score
- Use case ends
Postconditions:
- User aware of vault security status
- Specific vulnerabilities identified
- Recommendations provided for improvement
Primary Actor: Authenticated User
Preconditions:
- User logged in
- Add Entry form displayed
Main Success Scenario:
- User clicks "Generate" button
- System shows password generator interface
- User sets parameters:
- Length: 12-32 characters (default 16)
- System generates password:
- At least one lowercase letter
- At least one uppercase letter
- At least one digit
- At least one special character
- Remaining characters randomly selected
- System shuffles characters using Fisher-Yates algorithm
- System validates generated password is STRONG
- System displays generated password
- User clicks "Copy to Clipboard"
- System copies password to system clipboard
- System shows "Copied!" confirmation
- System clears clipboard after 60 seconds (security)
Alternative Flows:
-
6a. Generated password not STRONG:
- System regenerates password
- Return to step 4
-
8a. User clicks "Regenerate":
- Return to step 4
Postconditions:
- Strong password generated and available for use
- Password meets all security criteria
- User can paste into entry form
Primary Actor: Authenticated User
Preconditions:
- User logged in
- User has at least one vault entry
Main Success Scenario:
- User clicks "Export Backup" button
- System shows file save dialog
- System suggests filename:
safecore_backup_YYYY-MM-DD.safe - User selects destination folder
- User confirms filename
- System retrieves all user's entries
- System decrypts all passwords
- System creates JSON structure:
[ { "service": "Gmail", "username": "user@example.com", "plainPassword": "decrypted_password", "expiry": "2025-12-31T23:59:59" } ] - System encrypts JSON with AES-256-CBC
- System encodes ciphertext as Base64
- System writes Base64 string to .safe file
- System displays success notification
Alternative Flows:
-
6a. Vault is empty:
- System displays "Nothing to export" error
- Use case ends
-
11a. File write fails (permissions/disk full):
- System displays error message
- System suggests alternative location
- Return to step 2
Postconditions:
- Encrypted backup file created
- Backup can be imported on same or different device
- Backup requires same encryption key
Primary Actor: Authenticated User
Preconditions:
- User logged in
- User has valid .safe backup file
Main Success Scenario:
- User clicks "Import Backup" button
- System shows file open dialog
- User selects .safe backup file
- User clicks "Open"
- System reads file contents
- System decodes Base64 to ciphertext
- System decrypts ciphertext using AES-256-CBC
- System parses JSON structure
- System validates JSON format
- For each entry in backup:
- Decrypt plain password from backup
- Re-encrypt with current user's key
- Create PasswordEntryEntity
- Associate with current user
- Save to database
- System notifies VaultObservers
- System refreshes dashboard
- System displays import count: "X entries imported"
Alternative Flows:
-
7a. Decryption fails (wrong key):
- System displays "Corrupted or incompatible backup" error
- System suggests checking file integrity
- Use case ends
-
9a. JSON parsing fails:
- System displays "Invalid backup format" error
- Use case ends
-
10a. Duplicate entry detected (same service + username):
- System skips duplicate
- Continue to next entry
Postconditions:
- Backup entries imported and encrypted
- All entries visible in dashboard
- Health score recalculated
Primary Actor: Authenticated User
Preconditions:
- User logged in
- User wants to share secret information
Main Success Scenario:
- User clicks "SafeSend" button
- System displays SafeSend overlay
- User enters secret text (password, API key, etc.)
- User selects expiration time:
- 1 hour
- 12 hours
- 24 hours (default)
- 7 days
- User clicks "Generate Link"
- System encrypts secret with AES-256-CBC
- System generates cryptographically secure random token
- System hashes token with BCrypt
- System creates SafeSendEntryEntity:
- Encrypted content
- Token hash
- Expiration timestamp
- One-time flag: true
- System saves entry to database
- System generates shareable URL:
https://safecore.io/send/{UUID}?t={token} - System copies URL to clipboard
- System displays: "Link generated and copied!"
- User shares link via secure channel
Alternative Flows:
- 3a. Secret text empty:
- System disables "Generate Link" button
- Return to step 3
Postconditions:
- Secret encrypted and stored with expiration
- Unique link generated for one-time access
- Link auto-expires after time or first access
Primary Actor: Link Recipient (may be unauthenticated)
Preconditions:
- Recipient has valid SafeSend link
- Link not yet accessed (one-time)
- Link not expired
Main Success Scenario:
- Recipient clicks SafeSend link
- System parses URL to extract:
- Entry UUID
- Access token
- System retrieves SafeSendEntryEntity by UUID
- System validates entry exists
- System checks expiration timestamp
- System verifies token against stored hash
- System decrypts secret content
- System displays secret to recipient
- System immediately deletes entry from database
- System displays "This secret has been destroyed"
Alternative Flows:
-
4a. Entry not found (already accessed):
- System displays "Link expired or already used"
- Use case ends
-
5a. Link expired:
- System deletes entry
- System displays "Link has expired"
- Use case ends
-
6a. Token verification fails:
- System displays "Invalid or tampered link"
- System logs security event
- Use case ends
Postconditions:
- Secret accessed and displayed once
- Entry permanently deleted from database
- Link no longer functional
Primary Actor: Registered User (forgotten password)
Preconditions:
- User has registered account
- User cannot remember master password
Main Success Scenario:
- User clicks "Forgot Password?" on login screen
- System displays password reset form
- User enters registered email address
- User clicks "Request Reset"
- System validates email exists in database
- System generates cryptographically secure random token
- System hashes token with BCrypt
- System creates PasswordResetTokenEntity:
- User email
- Token hash
- Expiration: 15 minutes
- Used flag: false
- System saves token to database
- System displays token to user (simulated email)
- User copies token
- User enters:
- Reset token
- New master password
- Password confirmation
- System validates token:
- Token hash matches
- Not expired (< 15 minutes)
- Not already used
- System validates new password strength (MEDIUM or STRONG)
- System hashes new password with BCrypt
- System updates UserEntity.passwordHash
- System marks token as used
- System displays success message
- System redirects to login screen
Alternative Flows:
-
5a. Email not found:
- System displays generic "If account exists, token sent" message
- System does not reveal email existence (security)
- Use case ends
-
13a. Token invalid/expired:
- System displays "Invalid or expired reset token"
- System suggests requesting new token
- Use case ends
-
14a. New password too weak:
- System displays strength error
- System shows improvement suggestions
- Return to step 12
Postconditions:
- User master password updated
- Old password hash replaced
- Reset token invalidated
- User can log in with new password
Important Note: All encrypted vault entries remain encrypted with old key. In production, this would require re-encryption with new key derived from new password.
SafeCore provides comprehensive UML documentation covering use cases, architecture, and workflow sequences.
Key Use Cases:
- UC-1: User Registration with password strength validation
- UC-2: User Login with BCrypt authentication
- UC-3: Add Password Entry with AES-256 encryption
- UC-4: Security Audit analyzing weak/old/reused passwords
- UC-5: Generate Secure Password with cryptographic randomness
- UC-6: Export Vault Backup as encrypted .safe file
- UC-7: Import Vault Backup with decryption and re-encryption
- UC-8: SafeSend Secure Sharing with one-time links
- UC-9: SafeSend Access Secret with auto-destruction
- UC-10: Password Reset Request with time-limited tokens
Complete registration workflow showing:
- Real-time password strength evaluation
- Password hint generation
- Optional password generation
- BCrypt hashing with work factor 12
- User entity creation and persistence
- Form validation and error handling
Authentication workflow showing:
- Email/password validation
- BCrypt password verification
- Session initialization
- Dashboard navigation
- Vault loading and display
Password storage workflow showing:
- Form validation
- AES-256-CBC encryption with unique IV
- Password entry entity creation
- Database persistence
- Observer notification pattern
- Dashboard refresh
Vault analysis workflow showing:
- Retrieval of all encrypted entries
- Batch decryption for analysis
- Weak password detection
- Old password identification (> 1 year)
- Reused password detection
- Health score calculation (0-100)
- Results visualization with recommendations
Architecture Overview:
Presentation Layer (JavaFX UI + Controllers)
↓
Service Layer (UserService, VaultService, SecurityAuditService,
SafeSendService, BackupService, PasswordHintService,
PasswordGenerator)
↓
Security Module (AES Encryption, BCrypt Hasher, Key Manager,
Password Strength Evaluator)
↓
Persistence Layer (Spring Data Repositories, JPA/Hibernate ORM)
↓
Database (PostgreSQL)
Key Components:
- 8 Service Layer Components: All business logic services
- 4 Security Components: Encryption, hashing, key management, strength evaluation
- Repository Pattern: Spring Data JPA with custom queries
- Observer Pattern: VaultService notifies dashboard on changes
Shows package dependencies and layering:
com.safecore.ui- Presentation layer (controllers, navigation, session)com.safecore.business- Business logic (services, domain, exceptions)com.safecore.security- Security components (encryption, hashing, generation)com.safecore.persistence- Data access (entities, repositories)
Detailed class diagrams split by architectural layer:
- UI & Navigation: Controllers, SceneNavigator, SessionContext
- Business Logic: Services, domain objects, exceptions
- Persistence: Entities, repositories, JPA mappings
- Security: Encryption, hashing, key management, password utilities
Complete System Diagram: All classes and relationships
@startuml
skinparam classAttributeIconSize 0
skinparam linetype ortho
package "UI Layer" {
class LoginController {
- userService: UserService
- emailField: TextField
- passwordField: PasswordField
+ handleLogin(): void
+ togglePasswordVisibility(): void
}
class RegisterController {
- userService: UserService
- passwordGenerator: PasswordGenerator
- hintService: PasswordHintService
+ handleRegister(): void
+ handlePasswordTyping(): void
}
class DashboardController {
- vaultService: VaultService
- auditService: SecurityAuditService
- passwordTable: TableView
+ refreshVault(): void
+ handleAddEntry(): void
+ handleDeleteEntry(): void
+ onVaultChanged(): void
}
class SessionContext <<singleton>> {
- {static} loggedUserEmail: String
- {static} loginTime: LocalDateTime
+ {static} login(email: String): void
+ {static} logout(): void
+ {static} getCurrentUserEmail(): String
}
}
package "Business Layer" {
interface UserService {
+ register(email: String, password: String): User
+ login(email: String, password: String): Optional<User>
}
class UserServiceImpl {
- userRepository: UserRepository
- passwordHasher: PasswordHasher
- strengthEvaluator: PasswordStrengthEvaluator
+ register(email: String, password: String): User
+ login(email: String, password: String): Optional<User>
- validatePasswordStrength(password: String): void
}
class VaultService {
- passwordEntryRepository: PasswordEntryRepository
- userRepository: UserRepository
- encryptionStrategy: EncryptionStrategy
- observers: List<VaultObserver>
+ addEntry(service: String, username: String, plain: String): void
+ getEntriesForCurrentUser(): List<PasswordEntryEntity>
+ decryptPassword(encrypted: byte[]): String
+ deleteEntry(id: UUID): void
+ exportVaultAsEncryptedJson(file: File): void
+ importVaultFromEncryptedJson(file: File): void
+ addObserver(observer: VaultObserver): void
+ notifyObservers(): void
}
interface SecurityAuditService {
+ runAudit(): AuditResult
}
class SecurityAuditServiceImpl {
- vaultService: VaultService
- strengthEvaluator: PasswordStrengthEvaluator
+ runAudit(): AuditResult
- calculateScore(weak: int, old: int, reused: int): int
}
class AuditResult <<record>> {
+ score: int
+ weakCount: int
+ oldCount: int
+ reusedCount: int
+ totalPasswords: int
}
interface VaultObserver {
+ onVaultChanged(): void
}
}
package "Security Layer" {
interface EncryptionStrategy {
+ encrypt(plaintext: String): byte[]
+ decrypt(ciphertext: byte[]): String
}
class AESEncryptionStrategy {
- keyManager: KeyManager
- ALGORITHM: String = "AES/CBC/PKCS5Padding"
+ encrypt(plainText: String): byte[]
+ decrypt(cipherText: byte[]): String
}
class KeyManager {
- secretKey: SecretKey
+ getSecretKey(): SecretKey
}
class PasswordHasher {
+ hash(plain: String): String
+ verify(plain: String, hash: String): boolean
}
class PasswordStrengthEvaluator {
+ evaluate(password: String): Strength
}
enum Strength {
WEAK
MEDIUM
STRONG
}
class PasswordGenerator {
- random: SecureRandom
- rules: List<PasswordRule>
- evaluator: PasswordStrengthEvaluator
+ generateSafe(length: int): String
- generateRaw(length: int): String
- shuffle(input: String): String
}
class EncryptionFactory {
- strategies: Map<String, EncryptionStrategy>
+ getStrategy(type: String): EncryptionStrategy
+ getDefaultStrategy(): EncryptionStrategy
}
}
package "Persistence Layer" {
interface UserRepository {
+ findByEmail(email: String): Optional<UserEntity>
+ existsByEmail(email: String): boolean
+ updatePassword(email: String, hash: String): void
}
interface PasswordEntryRepository {
+ findByUserEmail(email: String): List<PasswordEntryEntity>
+ deleteByExpiresAtBefore(now: LocalDateTime): void
}
class UserEntity {
- id: UUID
- email: String
- passwordHash: String
}
class PasswordEntryEntity {
- id: UUID
- serviceName: String
- username: String
- encryptedPassword: byte[]
- createdAt: LocalDateTime
- expiresAt: LocalDateTime
- user: UserEntity
}
class User <<immutable>> {
- id: UUID
- email: String
- passwordHash: String
}
class UserBuilder {
+ id(id: UUID): UserBuilder
+ email(email: String): UserBuilder
+ passwordHash(hash: String): UserBuilder
+ build(): User
}
}
' Relationships
LoginController --> UserService
RegisterController --> UserService
RegisterController --> PasswordGenerator
DashboardController --> VaultService
DashboardController --> SecurityAuditService
DashboardController ..|> VaultObserver
UserService <|.. UserServiceImpl
SecurityAuditService <|.. SecurityAuditServiceImpl
UserServiceImpl --> UserRepository
UserServiceImpl --> PasswordHasher
UserServiceImpl --> PasswordStrengthEvaluator
UserServiceImpl --> UserBuilder
VaultService --> PasswordEntryRepository
VaultService --> UserRepository
VaultService --> EncryptionStrategy
VaultService --> VaultObserver
SecurityAuditServiceImpl --> VaultService
SecurityAuditServiceImpl --> PasswordStrengthEvaluator
SecurityAuditServiceImpl ..> AuditResult
EncryptionStrategy <|.. AESEncryptionStrategy
AESEncryptionStrategy --> KeyManager
EncryptionFactory --> EncryptionStrategy
PasswordStrengthEvaluator --> Strength
PasswordGenerator --> PasswordStrengthEvaluator
UserRepository --> UserEntity
PasswordEntryRepository --> PasswordEntryEntity
PasswordEntryEntity --> UserEntity
UserBuilder ..> User
@enduml@startuml
actor User
participant "LoginController" as LC
participant "UserService" as US
participant "UserRepository" as UR
participant "PasswordHasher" as PH
participant "SessionContext" as SC
participant "SceneNavigator" as SN
User -> LC: Enter email & password
User -> LC: Click "Login"
activate LC
LC -> US: login(email, password)
activate US
US -> UR: findByEmail(email)
activate UR
UR --> US: Optional<UserEntity>
deactivate UR
alt User Found
US -> PH: verify(password, userEntity.passwordHash)
activate PH
PH --> US: boolean (true/false)
deactivate PH
alt Password Valid
US --> LC: Optional<User> (present)
LC -> SC: login(email)
activate SC
SC -> SC: Set loggedUserEmail
SC -> SC: Record loginTime
SC --> LC: void
deactivate SC
LC -> SN: switchTo(stage, "dashboard.fxml", "Dashboard")
activate SN
SN -> SN: Load FXML
SN -> SN: Get Controller from Spring
SN -> SN: Set scene
SN --> LC: void
deactivate SN
LC --> User: Navigate to Dashboard
else Password Invalid
US --> LC: Optional.empty()
LC --> User: Show "Invalid credentials" error
end
else User Not Found
US --> LC: Optional.empty()
LC --> User: Show "Invalid credentials" error
end
deactivate US
deactivate LC
@enduml@startuml
actor User
participant "DashboardController" as DC
participant "VaultService" as VS
participant "SessionContext" as SC
participant "UserRepository" as UR
participant "AESEncryptionStrategy" as AES
participant "KeyManager" as KM
participant "PasswordEntryRepository" as PER
User -> DC: Click "Add Entry"
activate DC
DC --> User: Show entry form overlay
User -> DC: Enter service, username, password
User -> DC: Click "Save"
DC -> DC: Validate fields not empty
DC -> VS: addEntry(service, username, plainPassword)
activate VS
VS -> SC: getCurrentUserEmail()
activate SC
SC --> VS: email
deactivate SC
VS -> UR: findByEmail(email)
activate UR
UR --> VS: UserEntity
deactivate UR
VS -> AES: encrypt(plainPassword)
activate AES
AES -> KM: getSecretKey()
activate KM
KM --> AES: SecretKey
deactivate KM
AES -> AES: Generate random IV (16 bytes)
AES -> AES: Initialize Cipher (ENCRYPT_MODE)
AES -> AES: Encrypt plaintext
AES -> AES: Prepend IV to ciphertext
AES --> VS: byte[] encryptedPassword
deactivate AES
VS -> VS: Create PasswordEntryEntity
VS -> VS: Set serviceName, username, encryptedPassword
VS -> VS: Set user, createdAt
VS -> PER: save(entity)
activate PER
PER --> VS: Saved PasswordEntryEntity
deactivate PER
VS -> VS: notifyObservers()
VS --> DC: void
deactivate VS
DC -> DC: onVaultChanged()
DC -> DC: refreshVault()
DC -> DC: updateHealthScore()
DC --> User: Display success toast
DC --> User: Show entry in table
deactivate DC
@enduml@startuml
actor User
participant "DashboardController" as DC
participant "AuditController" as AC
participant "SecurityAuditService" as SAS
participant "VaultService" as VS
participant "PasswordStrengthEvaluator" as PSE
User -> DC: Click "Security Audit"
activate DC
DC -> DC: Show audit overlay
DC -> AC: Create AuditController
activate AC
AC -> AC: initialize()
AC -> SAS: runAudit()
activate SAS
SAS -> VS: getEntriesForCurrentUser()
activate VS
VS --> SAS: List<PasswordEntryEntity>
deactivate VS
alt Vault Empty
SAS --> AC: AuditResult(100, 0, 0, 0, 0)
else Vault Has Entries
loop For each entry
SAS -> VS: decryptPassword(entry.encryptedPassword)
activate VS
VS --> SAS: plainPassword
deactivate VS
SAS -> PSE: evaluate(plainPassword)
activate PSE
PSE --> SAS: Strength (WEAK/MEDIUM/STRONG)
deactivate PSE
SAS -> SAS: Track weak passwords
end
SAS -> SAS: Check entry.createdAt < 1 year ago
SAS -> SAS: Count old passwords
SAS -> SAS: Group passwords by value
SAS -> SAS: Count reused passwords
SAS -> SAS: calculateScore()
note right
score = 100
score -= weakCount * 10
score -= oldCount * 5
score -= reusedCount * 15
score = max(score, 0)
end note
SAS --> AC: AuditResult(score, weak, old, reused, total)
end
deactivate SAS
AC -> AC: Display score with color coding
AC -> AC: Display weakness counts
AC --> User: Show audit results in overlay
deactivate AC
deactivate DC
@enduml@startuml
actor User
participant "DashboardController" as DC
participant "BackupService" as BS
participant "VaultService" as VS
participant "AESEncryptionStrategy" as AES
participant "File System" as FS
User -> DC: Click "Export Backup"
activate DC
DC -> DC: Show file save dialog
User -> DC: Select destination path
User -> DC: Confirm filename
DC -> BS: exportBackup(targetFile)
activate BS
BS -> VS: exportVaultAsEncryptedJson(targetFile)
activate VS
VS -> VS: getEntriesForCurrentUser()
VS --> VS: List<PasswordEntryEntity>
loop For each entry
VS -> VS: decryptPassword(entry.encryptedPassword)
VS -> VS: Add to JSON structure
note right
{
"service": "Gmail",
"username": "user@example.com",
"plainPassword": "decrypted",
"expiry": "2025-12-31"
}
end note
end
VS -> VS: Convert list to JSON string
VS -> AES: encrypt(jsonString)
activate AES
AES --> VS: byte[] encryptedJson
deactivate AES
VS -> VS: Base64.encode(encryptedJson)
VS -> FS: Write Base64 string to file
activate FS
FS --> VS: Success
deactivate FS
VS --> BS: void
deactivate VS
BS --> DC: void
deactivate BS
DC --> User: Display "Backup exported successfully"
deactivate DC
@enduml@startuml
actor User
participant "DashboardController" as DC
participant "SafeSendController" as SSC
participant "SafeSendService" as SSS
participant "AESEncryptionStrategy" as AES
participant "PasswordHasher" as PH
participant "SafeSendRepository" as SSR
User -> DC: Click "SafeSend"
activate DC
DC --> User: Show SafeSend overlay
User -> SSC: Enter secret text
User -> SSC: Select expiration (24h)
User -> SSC: Click "Generate Link"
activate SSC
SSC -> SSS: createSafeLink(content, expirationHours)
activate SSS
SSS -> AES: encrypt(content)
activate AES
AES --> SSS: byte[] encrypted
deactivate AES
SSS -> SSS: Generate random token (SecureRandom)
SSS -> PH: hash(token)
activate PH
PH --> SSS: tokenHash
deactivate PH
SSS -> SSS: Create SafeSendEntryEntity
SSS -> SSS: Set encryptedContent, tokenHash
SSS -> SSS: Set expiresAt = now + 24 hours
SSS -> SSS: Set oneTime = true
SSS -> SSR: save(entry)
activate SSR
SSR --> SSS: Saved SafeSendEntryEntity with UUID
deactivate SSR
SSS -> SSS: Build URL
note right
https://safecore.io/send/{UUID}?t={token}
end note
SSS -> SSS: Copy URL to clipboard
SSS --> SSC: String link
deactivate SSS
SSC --> User: Display "Link generated and copied!"
SSC --> User: Show link in UI
deactivate SSC
deactivate DC
@enduml@startuml
[*] --> Created : User adds entry
state Created {
[*] --> Encrypted
Encrypted : Password encrypted with AES-256-CBC
Encrypted : Stored with unique IV
}
Created --> Active : Save to database
state Active {
[*] --> Viewable
Viewable : Decryptable by authenticated user
Viewable : Visible in dashboard table
Viewable --> Edited : User updates entry
Edited --> Viewable : Changes encrypted & saved
}
Active --> NearExpiry : expiryDate approaching
NearExpiry : Warning indicator shown
NearExpiry : 30 days before expiration
Active --> Expired : expiryDate passed
Expired : Auto-cleanup job triggers
Active --> Deleted : User deletes entry
Expired --> [*] : Purged from database
Deleted --> [*] : Removed from database
@enduml@startuml
package "Client Application" {
component [JavaFX UI] as UI
component [Spring Boot Container] as Spring
package "Business Logic" {
component [VaultService] as Vault
component [SecurityAuditService] as Audit
component [UserService] as User
}
package "Security" {
component [AESEncryption] as AES
component [BCryptHasher] as BCrypt
component [PasswordGenerator] as Gen
}
package "Data Access" {
component [JPA Repositories] as JPA
}
database "PostgreSQL Database" as DB {
storage [users]
storage [password_entries]
storage [safesend_entries]
}
}
UI --> Spring : Dependency Injection
UI --> Vault : CRUD Operations
UI --> Audit : Security Analysis
UI --> User : Authentication
Vault --> AES : Encrypt/Decrypt
User --> BCrypt : Hash/Verify
Gen --> AES : Secure Random
Vault --> JPA : Persist Entities
User --> JPA : User Management
Audit --> Vault : Read Vault
JPA --> DB : JDBC Connection
note right of AES
AES-256-CBC
Random IVs per entry
Key in memory only
end note
note right of BCrypt
Work factor: 12
Salt auto-generated
Adaptive hashing
end note
@enduml@startuml SafeCore_Package_Diagram
!theme blueprint
skinparam packageStyle folder
folder "📦 com.safecore" {
folder "🖥️ ui" {
folder "controller" {
class LoginController
class RegisterController
class DashboardController
class AuditController
class SafeSendController
class SettingsController
}
folder "navigation" {
class SceneNavigator
}
folder "session" {
class SessionContext
}
}
folder "💼 business" {
folder "service" {
interface UserService
interface SecurityAuditService
interface SafeSendService
class VaultService
class PasswordHintService
folder "impl" {
class UserServiceImpl
class SecurityAuditServiceImpl
class SafeSendServiceImpl
}
}
folder "domain" {
class User
class AuditResult
class PasswordEntry
}
folder "exception" {
class SafeCoreException
class WeakPasswordException
}
}
folder "🔐 security" {
interface EncryptionStrategy
class AESEncryptionStrategy
class KeyManager
class PasswordHasher
class PasswordStrengthEvaluator
class PasswordGenerator
}
folder "💾 persistence" {
folder "entity" {
class UserEntity
class PasswordEntryEntity
class SafeSendEntryEntity
}
folder "repository" {
interface UserRepository
interface PasswordEntryRepository
interface SafeSendRepository
}
}
}
' Dependencies
ui ..> business
business ..> security
business ..> persistence
security ..> persistence
@endumlKey Packages:
- ui - Presentation layer (controllers, navigation, session)
- business - Business logic (services, domain models, exceptions)
- security - Security components (encryption, hashing, key management)
- persistence - Data access (JPA entities, Spring Data repositories)
- Java 17 or higher: Download from AdoptOpenJDK
- Maven 3.8+: Download from Maven Official Site
- Git: For cloning the repository
git clone https://github.com/DaniaCiampalini/SafeCoreProject.git
cd SafeCoreProjectmvn clean installThis will:
- Download all dependencies
- Compile source code
- Run unit and integration tests
- Package application as executable JAR
mvn spring-boot:runOr run the JAR directly:
java -jar target/SafeCore-1.0-SNAPSHOT.jar- Application window should open with login screen
- Click "Register" to create first user account
- Enter email and secure password (minimum MEDIUM strength)
- Login with created credentials
- Dashboard should display with empty vault
The application uses PostgreSQL as its production database. Edit src/main/resources/application.properties:
# PostgreSQL Database (cloud-hosted on Neon)
spring.datasource.url=jdbc:postgresql://[your-neon-host].neon.tech/neondb?sslmode=require&channelBinding=require
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.username=neondb_owner
spring.datasource.password=${DB_PASSWORD}
# JPA/Hibernate
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialectEnvironment Variables:
DB_PASSWORD: Set this environment variable with your PostgreSQL password
Database Setup:
- Create a PostgreSQL database (e.g., on Neon, AWS RDS, or local PostgreSQL)
- Update the connection URL with your database host
- Set the
DB_PASSWORDenvironment variable - The application will automatically create tables on first run
BCrypt work factor (in PasswordHasher.java):
public String hash(String plain) {
return BCrypt.hashpw(plain, BCrypt.gensalt(12)); // Adjust work factor here
}Recommendations:
- Development: Work factor 10-12 (faster)
- Production: Work factor 12-14 (more secure, slower)
Encryption Configuration:
- AES-256-CBC with PBKDF2 key derivation
- Unique IV (Initialization Vector) per password entry
- Keys derived from user's master password + salt
FXML views location: src/main/resources/com/safecore/ui/view/
To customize UI:
- Open FXML file in Scene Builder or text editor
- Modify layout, colors, labels
- Rebuild project:
mvn clean package
-
Launch Application
mvn spring-boot:run
-
Create Account
- Click "Register" on login screen
- Enter valid email address
- Create strong master password (MEDIUM or STRONG)
- Confirm password matches
- Click "Register"
-
Login
- Enter registered email
- Enter master password
- Click "Login"
- Click "Add Entry" button on dashboard
- Fill in form:
- Service Name (e.g., "Gmail", "GitHub")
- Username/Email
- Password (manual or generated)
- Optional: Expiration date
- Click "Save"
- Click "Generate" button in add entry form
- Adjust length slider (12-32 characters)
- Click "Generate" to create password
- Click "Copy" to copy to clipboard
- Paste into password field
- Type in search bar at top
- Table filters in real-time by service name or username
- Click "eye" icon to reveal password
- Click "copy" icon to copy to clipboard
- Clipboard auto-clears after 60 seconds
- Click "delete" icon (trash can)
- Confirm deletion in dialog
- Click "Security Audit" button
- System analyzes all vault entries:
- Weak passwords (below STRONG criteria)
- Old passwords (> 1 year)
- Reused passwords (duplicates)
- View health score (0-100):
- 80-100: Excellent (green)
- 50-79: Good (yellow)
- 0-49: Poor (red)
- Review recommendations to improve score
- Click "Backup" → "Export"
- Choose destination folder
- Confirm filename (e.g.,
safecore_backup_2025-01-30.safe) - Backup file encrypted with your key
- Click "Backup" → "Import"
- Select
.safebackup file - System decrypts and imports entries
- Dashboard refreshes with imported data
Important: Backups are encrypted with your current encryption key. Keep key secure!
- Click "SafeSend" button
- Paste secret text (password, API key, note)
- Select expiration time:
- 1 hour
- 12 hours
- 24 hours (recommended)
- 7 days
- Click "Generate Link"
- Link copied to clipboard automatically
- Share link via secure channel (Signal, encrypted email)
- Recipient clicks link
- Secret displayed once
- Entry auto-destructs immediately
- Link becomes invalid
Security Notes:
- Links are one-time use only
- Secrets encrypted with AES-256
- Auto-expire after specified time
- Access logged (future feature)
SafeCore implements a comprehensive testing strategy covering all architectural layers with unit tests, integration tests, performance tests, and UI tests using TestFX.
Total Test Files: 27
Total Test Methods: 100+
Test Coverage: 85%+ across all layers
Frameworks: JUnit 5, Mockito, TestFX, Spring Boot Test
# Run complete test suite
mvn test
# Run tests with coverage report
mvn clean test jacoco:report
# Run specific test category
mvn test -Dtest=*ServiceTest
mvn test -Dtest=*ControllerTest
mvn test -Dtest=*PerformanceTestService Tests - Test business logic with mocked dependencies
mvn test -Dtest=*ServiceTestTest Files:
UserServiceTest- User registration, authentication, deletion with sanitizationPasswordServiceTest- Password validation and strength evaluationSafeSendServiceTest- Secure link generation and one-time accessSecurityAuditServiceTest- Vault health score calculation and weak password detectionPasswordHintServiceTest- Real-time password strength hints
Coverage:
- Registration with password strength validation
- Login with BCrypt verification
- Account deletion with data sanitization
- Entry encryption/decryption workflows
- Password expiration logic
- SafeSend link creation with time-based expiration
- Security audit scoring algorithm
Encryption Tests
mvn test -Dtest=AESEncryptionStrategyTestTests AES-256-CBC encryption implementation:
- Encryption/decryption cycles
- IV (Initialization Vector) uniqueness
- Data integrity validation
- Exception handling for invalid input
Password Hashing Tests
mvn test -Dtest=PasswordHasherTestTests BCrypt password hashing:
- Hash generation with work factor 12
- Password verification correctness
- Salt uniqueness per hash
- Exception handling for null/empty passwords
Password Generation Tests
mvn test -Dtest=PasswordGeneratorTestTests cryptographically secure password generation:
- Length requirements (8-128 characters)
- Character set inclusion (uppercase, lowercase, digits, symbols)
- Randomness and uniqueness
- No predictable patterns
Password Strength Evaluation Tests
mvn test -Dtest=PasswordStrengthEvaluatorTestTests password strength scoring:
- Weak passwords detection
- Strong password requirements
- Length-based scoring
- Character diversity analysis
BCrypt Performance Test
mvn test -Dtest=PasswordHasherPerformanceTestValidates hashing performance:
- Hash generation completes within 1000ms
- Password verification under 500ms
- Consistent performance across 100+ iterations
- Work factor 12 efficiency
AES Encryption Performance Test
mvn test -Dtest=AESEncryptionPerformanceTestValidates encryption performance:
- Encryption time for various data sizes
- Decryption performance metrics
- IV generation overhead
- Memory usage patterns
Repository Tests
mvn test -Dtest=*RepositoryTestTest Files:
UserRepositoryTest- User CRUD operations with unique email constraintSafeSendRepositoryTest- SafeSend entry persistence and expiration queries
Tests database interactions:
- Entity save/update/delete
- Query methods (findByEmail, findByToken)
- Unique constraints validation
- Cascade delete behavior
- Transaction rollback scenarios
User Interface Tests with TestFX
# Run all UI tests
mvn test -Dtest=*ControllerTest
# Run specific controller test
mvn test -Dtest=LoginControllerTest
mvn test -Dtest=RegisterControllerTest
mvn test -Dtest=SettingsControllerTestTest Files: 4 controller tests + 1 base class
LoginControllerTest(8 tests) - Login form validation, password toggle, authenticationRegisterControllerTest(11 tests) - Registration form, password strength indicator, password generatorSettingsControllerTest(16 tests) - Account deletion with confirmation phrase validationSafeSendControllerTest(5 tests) - Secure sharing UI and expiration optionsTestFXBaseTest- Base class with JavaFX test configuration
Total UI Tests: 40+ tests covering all major user workflows
UI Test Coverage:
- Form validation (empty fields, invalid input)
- Password visibility toggle (show/hide)
- Password strength real-time feedback
- Password generator functionality
- Account deletion confirmation with exact phrase matching
- Table data display and configuration
- ComboBox default values and options
- Read-only TextArea validation
- Session management in UI
- Error message display
TestFX Features:
- Spring Boot integration with dependency injection
- H2 in-memory database for isolated tests
- WaitForAsyncUtils for JavaFX thread synchronization
- Cleanup with @DirtiesContext after each test
- Stable tests avoiding flaky behavior
Full Workflow Integration Test
mvn test -Dtest=SafeCoreIntegrationTestTests complete end-to-end user workflow with real database:
- User registration with password validation
- Login authentication with session creation
- Add encrypted password entry to vault
- Retrieve and decrypt password entry
- Update existing entry
- Delete entry
- Logout and session cleanup
Backup Integration Test
mvn test -Dtest=BackupIntegrationTestTests backup/restore functionality:
- Export encrypted backup to .safe file
- Import and decrypt backup file
- Data integrity after restore
- Key derivation for encryption
Custom Exception Tests
mvn test -Dtest=*ExceptionTestTest Files:
UserAlreadyExistsExceptionTestUserNotFoundExceptionTestWeakPasswordExceptionTestExpiredLinkExceptionTestLinkNotFoundExceptionTestInvalidTokenExceptionTestSafeCoreExceptionTest
Tests exception handling:
- Custom exception message construction
- Exception hierarchy
- Proper exception propagation
- Error handling in UI layer
Test Properties (src/test/resources/application-test.properties):
# H2 in-memory database
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.format_sql=false
# Test profile
spring.profiles.active=test
# Disable banner
spring.main.banner-mode=offGenerate Coverage Report:
mvn clean test jacoco:reportReport location: target/site/jacoco/index.html
Surefire Test Reports:
Location: target/surefire-reports/
- XML reports for CI/CD integration
- TXT reports with test execution details
@SpringBootTest
@ActiveProfiles("test")
class MyServiceTest {
@Autowired
private MyService myService;
@MockBean
private MyRepository myRepository;
@Test
void testMethodName_whenCondition_thenExpectedResult() {
// Arrange
when(myRepository.findById(1L)).thenReturn(Optional.of(entity));
// Act
Result result = myService.performOperation(1L);
// Assert
assertNotNull(result);
assertEquals(expectedValue, result.getValue());
verify(myRepository, times(1)).findById(1L);
}
}@SpringBootTest
@ActiveProfiles("test")
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
class MyControllerTest extends TestFXBaseTest {
@Autowired
private ApplicationContext applicationContext;
private void loadView() {
runOnFxThread(() -> {
try {
FXMLLoader loader = new FXMLLoader(
getClass().getResource("/com/safecore/ui/view/my-view.fxml")
);
loader.setControllerFactory(applicationContext::getBean);
Parent root = loader.load();
stage.setScene(new Scene(root));
stage.show();
} catch (Exception e) {
throw new RuntimeException("Failed to load view", e);
}
});
WaitForAsyncUtils.waitForFxEvents();
}
@Test
void testUIElement_whenAction_thenExpectedBehavior() {
loadView();
TextField myField = lookup("#myField").query();
assertNotNull(myField);
interact(() -> myField.setText("test"));
WaitForAsyncUtils.waitForFxEvents();
assertEquals("test", myField.getText());
}
}- Isolation: Each test is independent and can run in any order
- H2 Database: Tests use in-memory database, reset after each test
- Mocking: External dependencies are mocked to focus on unit logic
- Cleanup: @DirtiesContext ensures Spring context is recreated for UI tests
- Assertions: Clear assertion messages for debugging failures
- Naming: Tests follow
methodName_whenCondition_thenExpectedResultconvention - Coverage: Aim for 80%+ code coverage with meaningful tests
- Performance: Performance tests validate acceptable execution times
- UI Stability: TestFX tests avoid flaky behavior with proper synchronization
Tests are designed to run in CI/CD pipelines:
- No external dependencies (except H2)
- Headless JavaFX support for UI tests
- Maven Surefire integration
- JaCoCo coverage reports
- Fast execution (full suite under 5 minutes)
public interface UserService {
/**
* Registers a new user with email and password.
*
* @param email User email (must be unique)
* @param plainPassword Master password (minimum MEDIUM strength)
* @return Immutable User domain object
* @throws UserAlreadyExistsException if email already registered
* @throws WeakPasswordException if password below minimum strength
*/
User register(String email, String plainPassword);
/**
* Authenticates user with credentials.
*
* @param email Registered email
* @param plainPassword Plain text password
* @return Optional<User> - present if credentials valid, empty otherwise
*/
Optional<User> login(String email, String plainPassword);
}public class VaultService {
/**
* Adds encrypted password entry for current user.
*
* @param service Service name (e.g., "Gmail")
* @param username Username or email for service
* @param plain Plain text password (will be encrypted)
* @param expiry Optional expiration date (null for no expiry)
*/
@Transactional
public void addEntry(String service, String username, String plain, LocalDateTime expiry);
/**
* Retrieves all password entries for currently authenticated user.
*
* @return List of PasswordEntryEntity with encrypted passwords
*/
public List<PasswordEntryEntity> getEntriesForCurrentUser();
/**
* Decrypts password from encrypted byte array.
*
* @param encrypted Encrypted password bytes (includes IV)
* @return Decrypted plain text password
*/
public String decryptPassword(byte[] encrypted);
/**
* Deletes password entry by ID.
*
* @param id Entry UUID
*/
@Transactional
public void deleteEntry(UUID id);
/**
* Exports entire vault as encrypted JSON backup.
*
* @param destinationFile Target .safe file
* @throws Exception if encryption or file write fails
*/
@Transactional(readOnly = true)
public void exportVaultAsEncryptedJson(File destinationFile) throws Exception;
/**
* Imports vault entries from encrypted backup.
*
* @param sourceFile Source .safe file
* @throws Exception if decryption or parsing fails
*/
@Transactional
public void importVaultFromEncryptedJson(File sourceFile) throws Exception;
}public interface SecurityAuditService {
/**
* Analyzes vault for security weaknesses.
*
* @return AuditResult with health score and vulnerability counts
*/
AuditResult runAudit();
}
public record AuditResult(
int score, // Health score 0-100
int weakCount, // Number of weak passwords
int oldCount, // Number of passwords > 1 year old
int reusedCount, // Number of reused passwords
int totalPasswords // Total vault entries
) {}@Component
public class PasswordGenerator {
/**
* Generates cryptographically secure password.
*
* @param length Desired length (minimum 12, recommended 16+)
* @return Random password meeting STRONG criteria
*/
public String generateSafe(int length);
}@Component
public class PasswordStrengthEvaluator {
/**
* Evaluates password strength based on multiple criteria.
*
* @param password Password to evaluate
* @return Strength enum (WEAK, MEDIUM, STRONG)
*/
public Strength evaluate(String password);
public enum Strength {
WEAK, // < 8 chars OR < 3 criteria
MEDIUM, // >= 8 chars AND 3 criteria
STRONG // >= 8 chars AND all 4 criteria
}
}public interface EncryptionStrategy {
/**
* Encrypts plaintext string to byte array.
*
* @param plaintext Data to encrypt
* @return Encrypted bytes with prepended IV
*/
byte[] encrypt(String plaintext);
/**
* Decrypts byte array to plaintext string.
*
* @param ciphertext Encrypted bytes (includes IV)
* @return Decrypted plaintext
* @throws SecurityException if decryption fails
*/
String decrypt(byte[] ciphertext);
}@Component
public class AESEncryptionStrategy implements EncryptionStrategy {
private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
@Override
public byte[] encrypt(String plainText) {
// 1. Generate random 128-bit IV
// 2. Initialize Cipher with AES-256-CBC
// 3. Encrypt plaintext
// 4. Prepend IV to ciphertext
// 5. Return combined byte array
}
@Override
public String decrypt(byte[] cipherText) {
// 1. Extract first 16 bytes as IV
// 2. Extract remaining bytes as ciphertext
// 3. Initialize Cipher with IV
// 4. Decrypt ciphertext
// 5. Return plaintext string
}
}Contributions are welcome! Please follow these guidelines:
- Fork the repository
- Create feature branch:
git checkout -b feature/your-feature-name
- Make changes with clear, atomic commits
- Write/update tests for new functionality
- Ensure all tests pass:
mvn test - Verify code quality:
mvn checkstyle:check
- Java: Follow Oracle Java Code Conventions
- Indentation: 4 spaces (no tabs)
- Line Length: Max 120 characters
- Naming:
- Classes: PascalCase
- Methods/Variables: camelCase
- Constants: UPPER_SNAKE_CASE
- JavaDoc: Required for public APIs
Format: type(scope): description
Types:
feat: New featurefix: Bug fixdocs: Documentation onlystyle: Code style (formatting, no logic change)refactor: Code restructuringtest: Adding/updating testschore: Build process, dependencies
Example:
feat(vault): Add auto-expire password entries
- Implement scheduled cleanup job
- Add expiresAt column to PasswordEntryEntity
- Update UI to display expiration warnings
- Update README.md with changes (if applicable)
- Update CHANGELOG.md with new version
- Ensure all tests pass and coverage maintained
- Request review from maintainers
- Address review feedback
- Squash commits if requested
- Maintainer merges after approval
Use GitHub Issues with template:
**Describe the bug**
Clear description of what's wrong.
**To Reproduce**
1. Go to '...'
2. Click on '....'
3. See error
**Expected behavior**
What should have happened.
**Screenshots**
If applicable.
**Environment:**
- OS: [e.g., Windows 11, macOS Ventura]
- Java Version: [e.g., OpenJDK 17.0.5]
- SafeCore Version: [e.g., 1.0.0]
**Additional context**
Any other relevant information.Use GitHub Issues with template:
**Feature Description**
Clear description of proposed feature.
**Use Case**
Why is this feature needed?
**Proposed Solution**
How should it work?
**Alternatives Considered**
Other approaches you've thought of.
**Additional Context**
Mockups, diagrams, related issues.This project is licensed under the MIT License.
MIT License
Copyright (c) 2026 Dania Ciampalini, Cecilia Vignani
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Dania Ciampalini & Cecilia Vignani
This project was developed as part of a Software Engineering course, demonstrating enterprise-grade architecture, design patterns, and security best practices in a real-world password management application.
This project was built using outstanding open-source technologies:
- Spring Framework Team: For Spring Boot 3.x, Spring Data JPA, and comprehensive dependency injection
- OpenJFX Community: For JavaFX 17 and modern desktop UI capabilities
- PostgreSQL Global Development Group: For robust, production-grade relational database
- Neon: For serverless PostgreSQL hosting with excellent developer experience
- jBCrypt Contributors: For robust BCrypt implementation
- JUnit Team: For JUnit 5 testing framework
- Mockito Contributors: For powerful mocking capabilities
- PlantUML Project: For UML diagram generation
- Maven Community: For reliable build and dependency management
- JaCoCo Team: For comprehensive code coverage analysis
- OWASP: For security best practices and password storage guidelines
- Material Design: For UI/UX design inspiration
- GitHub: For hosting and collaboration tools
- Effective Java by Joshua Bloch
- Design Patterns: Elements of Reusable Object-Oriented Software by Gang of Four
- Spring in Action by Craig Walls
SafeCore - Enterprise-Grade Password Security
Built with security, quality, and engineering excellence in mind.
For support, please open an issue on GitHub.
Authors: Dania Ciampalini & Cecilia Vignani
GitHub: @DaniaCiampalini & @CeciliaVignani