Update Readme Files
This commit is contained in:
175
AGENTS.md
175
AGENTS.md
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
This is a Python password hashing utility supporting multiple cryptographic algorithms (PBKDF2, Argon2, bcrypt). The project provides both library functions and two CLI implementations with different UX patterns.
|
This is a Python password hashing utility supporting multiple cryptographic algorithms (PBK-DF2, Argon2, bcrypt). The project provides both library functions and two CLI implementations with different UX patterns.
|
||||||
|
|
||||||
**Key Architecture:**
|
**Key Architecture:**
|
||||||
- **Algorithm Registry Pattern**: Pluggable hash algorithms implementing a common `Algorithm` protocol
|
- **Algorithm Registry Pattern**: Pluggable hash algorithms implementing a common `Algorithm` protocol
|
||||||
@@ -21,7 +21,9 @@ bcrypt_algorithm.py # bcrypt implementation
|
|||||||
tests/
|
tests/
|
||||||
test_hashing.py # Tests for core hash/verify functions
|
test_hashing.py # Tests for core hash/verify functions
|
||||||
test_cli.py # Tests for salt.py CLI behavior
|
test_cli.py # Tests for salt.py CLI behavior
|
||||||
|
test_cli2.py # Tests for salt2.py CLI behavior
|
||||||
test_algorithms.py # Tests for algorithm implementations
|
test_algorithms.py # Tests for algorithm implementations
|
||||||
|
test_integration.py # End-to-end tests for CLI tools
|
||||||
```
|
```
|
||||||
|
|
||||||
## Essential Commands
|
## Essential Commands
|
||||||
@@ -36,21 +38,23 @@ python3 salt2.py generate "MyPassword" # Alternative CLI
|
|||||||
|
|
||||||
# Hash with specific algorithm
|
# Hash with specific algorithm
|
||||||
python3 salt.py hash --algorithm argon2 "MyPassword"
|
python3 salt.py hash --algorithm argon2 "MyPassword"
|
||||||
python3 salt.py --algorithm bcrypt "MyPassword" # Shortcut with algorithm
|
python3 salt.py --algorithm bcrypt "MyPassword" # Shortcut with algorithm
|
||||||
python3 salt2.py generate "MyPassword" # Note: salt2.py doesn't support --algorithm yet
|
python3 salt2.py generate --algorithm argon2 "MyPassword"
|
||||||
|
|
||||||
# Verify a password
|
# Verify a password
|
||||||
python3 salt.py verify "MyPassword" <salt_b64> <hash_b64>
|
python3 salt.py verify "MyPassword" <salt_b64> <hash_b64>
|
||||||
python3 salt.py verify --algorithm argon2 "MyPassword" "" <hash_b64>
|
python3 salt.py verify --algorithm argon2 "MyPassword" "" <hash_b64>
|
||||||
|
python3 salt2.py verify --algorithm bcrypt "MyPassword" "" <hash_b64>
|
||||||
|
|
||||||
# List available algorithms
|
# List available algorithms
|
||||||
python3 salt.py list-algorithms # Not implemented yet, would be useful addition
|
python3 salt.py list-algorithms
|
||||||
|
python3 salt2.py list-algorithms
|
||||||
```
|
```
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run all tests (20 tests total as of now)
|
# Run all tests (28 tests total as of now)
|
||||||
python3 -m pytest
|
python3 -m pytest
|
||||||
|
|
||||||
# Verbose output
|
# Verbose output
|
||||||
@@ -60,6 +64,7 @@ python3 -m pytest -v
|
|||||||
python3 -m pytest tests/test_hashing.py
|
python3 -m pytest tests/test_hashing.py
|
||||||
python3 -m pytest tests/test_cli.py
|
python3 -m pytest tests/test_cli.py
|
||||||
python3 -m pytest tests/test_algorithms.py
|
python3 -m pytest tests/test_algorithms.py
|
||||||
|
python3 -m pytest tests/test_integration.py
|
||||||
|
|
||||||
# Run specific test patterns
|
# Run specific test patterns
|
||||||
python3 -m pytest -k verify
|
python3 -m pytest -k verify
|
||||||
@@ -155,7 +160,7 @@ python3 salt.py --algorithm argon2 "mypassword"
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Normalization rules:**
|
**Normalization rules:**
|
||||||
1. If first arg is `hash` or `verify` → pass through unchanged
|
1. If first arg is `hash`, `verify`, or `list-algorithms` → pass through unchanged
|
||||||
2. If first arg is `-h` or `--help` → pass through unchanged
|
2. If first arg is `-h` or `--help` → pass through unchanged
|
||||||
3. If first arg is `--algorithm` or `-a` → prepend `hash`
|
3. If first arg is `--algorithm` or `-a` → prepend `hash`
|
||||||
4. Otherwise (plain password) → prepend `hash`
|
4. Otherwise (plain password) → prepend `hash`
|
||||||
@@ -216,7 +221,9 @@ DEFAULT_ITERATIONS = int(os.environ.get("PBKDF2_ITERATIONS", "200000"))
|
|||||||
**Test organization:**
|
**Test organization:**
|
||||||
- `test_hashing.py`: Core `hash_password()` and `verify_password()` functions
|
- `test_hashing.py`: Core `hash_password()` and `verify_password()` functions
|
||||||
- `test_cli.py`: CLI behavior for `salt.py` (including shortcut syntax)
|
- `test_cli.py`: CLI behavior for `salt.py` (including shortcut syntax)
|
||||||
|
- `test_cli2.py`: CLI behavior for `salt2.py`
|
||||||
- `test_algorithms.py`: Individual algorithm implementations
|
- `test_algorithms.py`: Individual algorithm implementations
|
||||||
|
- `test_integration.py`: End-to-end integration tests for CLI commands
|
||||||
|
|
||||||
### Testing Patterns
|
### Testing Patterns
|
||||||
|
|
||||||
@@ -246,8 +253,8 @@ Exit codes: 0 = success, 1 = verification failure
|
|||||||
|
|
||||||
### Test Execution Notes
|
### Test Execution Notes
|
||||||
|
|
||||||
- 20 tests total (as of current state)
|
- 28 tests total (as of current state)
|
||||||
- Test execution time: ~5 seconds (mostly from Argon2/bcrypt hashing)
|
- Test execution time: ~7 seconds (mostly from Argon2/bcrypt hashing)
|
||||||
- All tests must pass before committing
|
- All tests must pass before committing
|
||||||
- Use `pytest -v` for verbose output
|
- Use `pytest -v` for verbose output
|
||||||
- Use `pytest -k <pattern>` to run subset
|
- Use `pytest -k <pattern>` to run subset
|
||||||
@@ -270,7 +277,7 @@ def test_<algo>_algorithm_identifier():
|
|||||||
assert algo.identifier == "<algo>"
|
assert algo.identifier == "<algo>"
|
||||||
```
|
```
|
||||||
|
|
||||||
And to `tests/test_cli.py`:
|
And to `tests/test_cli.py` or `tests/test_cli2.py`:
|
||||||
```python
|
```python
|
||||||
def test_main_hash_with_algorithm_<algo>():
|
def test_main_hash_with_algorithm_<algo>():
|
||||||
"""Test hash command with --algorithm <algo>."""
|
"""Test hash command with --algorithm <algo>."""
|
||||||
@@ -285,7 +292,7 @@ The imports at the bottom of `algorithms.py` trigger registration. If you import
|
|||||||
|
|
||||||
### 2. CLI Output to Stdout (Tests Must Capture)
|
### 2. CLI Output to Stdout (Tests Must Capture)
|
||||||
|
|
||||||
The CLI functions print to stdout. Tests use `main([...])` to check exit codes but don't capture output. If you need to test output content, use `capsys` fixture:
|
The CLI functions print to stdout. Tests use `main([...])` to check exit codes but don't capture output. If you need to test output content, use `capsys` fixture. Subprocess-based integration tests also capture stdout.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def test_output_format(capsys):
|
def test_output_format(capsys):
|
||||||
@@ -315,21 +322,11 @@ DEFAULT_ITERATIONS = int(os.environ.get("PBKDF2_ITERATIONS", "200000"))
|
|||||||
CLI output and help text are in German:
|
CLI output and help text are in German:
|
||||||
- `"✓ Passwort korrekt"` / `"✗ Passwort falsch"`
|
- `"✓ Passwort korrekt"` / `"✗ Passwort falsch"`
|
||||||
- Help text uses German descriptions
|
- Help text uses German descriptions
|
||||||
- Comments in code are mixed German/English
|
|
||||||
- When adding CLI features, maintain German for user-facing text
|
- When adding CLI features, maintain German for user-facing text
|
||||||
|
|
||||||
### 6. Exception Handling in Verify
|
### 6. Exception Handling in Verify
|
||||||
|
|
||||||
`verify_password()` catches exceptions and returns `False` rather than raising:
|
`verify_password()` and algorithm implementations catch exceptions and return `False` rather than raising. This is intentional for security to avoid leaking information via exception types.
|
||||||
```python
|
|
||||||
try:
|
|
||||||
salt = base64.b64decode(salt_b64, validate=True)
|
|
||||||
stored_hash = base64.b64decode(hash_b64, validate=True)
|
|
||||||
except (binascii.Error, ValueError):
|
|
||||||
return False
|
|
||||||
```
|
|
||||||
|
|
||||||
This is intentional for security (avoid leaking information via exception types).
|
|
||||||
|
|
||||||
### 7. Dynamic Algorithm List
|
### 7. Dynamic Algorithm List
|
||||||
|
|
||||||
@@ -346,15 +343,7 @@ hash_parser.add_argument(
|
|||||||
|
|
||||||
This means adding a new algorithm automatically adds it to CLI choices.
|
This means adding a new algorithm automatically adds it to CLI choices.
|
||||||
|
|
||||||
### 8. salt2.py Missing Algorithm Support
|
### 8. Relative Imports vs Absolute Imports
|
||||||
|
|
||||||
**Current limitation:** `salt2.py` does NOT yet support the `--algorithm` flag. It only uses the default (PBKDF2). To add support:
|
|
||||||
1. Update `_build_parser()` to add `--algorithm` argument to both subparsers
|
|
||||||
2. Update `_command_generate()` to pass `algorithm=args.algorithm`
|
|
||||||
3. Update `_command_verify()` to pass `algorithm=args.algorithm`
|
|
||||||
4. Add tests to `tests/test_cli.py` or create `tests/test_cli2.py`
|
|
||||||
|
|
||||||
### 9. Relative Imports vs Absolute Imports
|
|
||||||
|
|
||||||
The code uses **absolute imports** (not relative):
|
The code uses **absolute imports** (not relative):
|
||||||
```python
|
```python
|
||||||
@@ -367,7 +356,6 @@ This works because modules are in the root directory, not a package. Keep this p
|
|||||||
|
|
||||||
### Cryptographic Randomness
|
### Cryptographic Randomness
|
||||||
- Always use `os.urandom()` for salt generation (never `random` module)
|
- Always use `os.urandom()` for salt generation (never `random` module)
|
||||||
- Never use predictable values or timestamps
|
|
||||||
|
|
||||||
### Timing Attack Protection
|
### Timing Attack Protection
|
||||||
- Use `hmac.compare_digest()` for hash comparison (PBKDF2)
|
- Use `hmac.compare_digest()` for hash comparison (PBKDF2)
|
||||||
@@ -381,12 +369,10 @@ This works because modules are in the root directory, not a package. Keep this p
|
|||||||
### Iteration Count
|
### Iteration Count
|
||||||
- Default 200,000 for PBKDF2 (OWASP recommended as of 2023)
|
- Default 200,000 for PBKDF2 (OWASP recommended as of 2023)
|
||||||
- Configurable via `PBKDF2_ITERATIONS` environment variable
|
- Configurable via `PBKDF2_ITERATIONS` environment variable
|
||||||
- Always parameterize in functions (don't hardcode)
|
|
||||||
|
|
||||||
### No Logging of Secrets
|
### No Logging of Secrets
|
||||||
- Never log passwords, salts, or hashes
|
- Never log passwords, salts, or hashes
|
||||||
- Print statements only in CLI code for user output
|
- Print statements only in CLI code for user output
|
||||||
- Tests don't need to capture sensitive values
|
|
||||||
|
|
||||||
## Configuration and Environment
|
## Configuration and Environment
|
||||||
|
|
||||||
@@ -394,7 +380,7 @@ This works because modules are in the root directory, not a package. Keep this p
|
|||||||
|
|
||||||
**`PBKDF2_ITERATIONS`**
|
**`PBKDF2_ITERATIONS`**
|
||||||
- Default: `"200000"` (as string, converted to int)
|
- Default: `"200000"` (as string, converted to int)
|
||||||
- Used by: `pbkdf2_algorithm.py` and `salt.py`
|
- Used by: `pbkdf2_algorithm.py`
|
||||||
- Example: `export PBKDF2_ITERATIONS=300000`
|
- Example: `export PBKDF2_ITERATIONS=300000`
|
||||||
|
|
||||||
### Dependencies (requirements.txt)
|
### Dependencies (requirements.txt)
|
||||||
@@ -405,149 +391,38 @@ argon2-cffi>=23.1.0 # Argon2id implementation
|
|||||||
bcrypt>=4.1.0 # bcrypt implementation
|
bcrypt>=4.1.0 # bcrypt implementation
|
||||||
```
|
```
|
||||||
|
|
||||||
**Standard library only:** `hashlib`, `hmac`, `os`, `base64`, `binascii`, `argparse`, `sys`
|
|
||||||
|
|
||||||
### Python Version Requirements
|
### Python Version Requirements
|
||||||
|
|
||||||
- **Minimum:** Python 3.11 (for `|` union type syntax)
|
- **Minimum:** Python 3.11 (for `|` union type syntax)
|
||||||
- **Tested on:** Python 3.12.3
|
- **Tested on:** Python 3.12.3
|
||||||
- **Type hints:** Using modern syntax (`int | None`, not `Optional[int]`)
|
|
||||||
|
|
||||||
## Commit Guidelines
|
## Commit Guidelines
|
||||||
|
|
||||||
### Commit Message Format
|
### Commit Message Format
|
||||||
|
|
||||||
Use conventional commit format with imperative mood:
|
Use conventional commit format with imperative mood:
|
||||||
|
|
||||||
```
|
```
|
||||||
<type>: <short description>
|
<type>: <short description>
|
||||||
|
|
||||||
<optional longer description>
|
|
||||||
|
|
||||||
<optional footer>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Types in use:**
|
|
||||||
- `feat:` - New features (e.g., "feat: add argon2 algorithm")
|
|
||||||
- `fix:` - Bug fixes
|
|
||||||
- `test:` - Test additions/changes
|
|
||||||
- `refactor:` - Code restructuring without behavior change
|
|
||||||
- `docs:` - Documentation updates
|
|
||||||
|
|
||||||
### Examples from Git History
|
|
||||||
|
|
||||||
```
|
|
||||||
docs: add README.md for repository
|
|
||||||
docs: translate CLAUDE.md to German
|
|
||||||
first commit
|
|
||||||
```
|
```
|
||||||
|
**Types in use:** `feat`, `fix`, `test`, `refactor`, `docs`.
|
||||||
|
|
||||||
### Before Committing
|
### Before Committing
|
||||||
|
|
||||||
1. Run full test suite: `python3 -m pytest -v`
|
1. Run full test suite: `python3 -m pytest -v`
|
||||||
2. Ensure all tests pass (20/20)
|
2. Ensure all tests pass (28/28)
|
||||||
3. Verify no regressions in existing functionality
|
3. Verify no regressions
|
||||||
4. Check that new code follows style conventions
|
4. Add tests for new functionality
|
||||||
5. Add tests for new functionality
|
|
||||||
|
|
||||||
### Pull Request Guidelines
|
|
||||||
|
|
||||||
- Keep PRs focused on single feature/fix
|
|
||||||
- Include motivation in description
|
|
||||||
- Reference issues with `Fixes #ID`
|
|
||||||
- For security changes: include CLI output or reproduction
|
|
||||||
- All tests must pass
|
|
||||||
- Maintain >90% coverage
|
|
||||||
|
|
||||||
## Planned Enhancements and Known Gaps
|
|
||||||
|
|
||||||
Based on existing documentation and code:
|
|
||||||
|
|
||||||
### Missing Features (mentioned in plans but not implemented)
|
|
||||||
|
|
||||||
1. **`list-algorithms` command** - Mentioned in plan (Task 9) but not in current code
|
|
||||||
2. **salt2.py algorithm support** - `salt2.py` doesn't support `--algorithm` flag yet
|
|
||||||
3. **Integration tests** - `tests/test_integration.py` mentioned in plan but not created
|
|
||||||
4. **Backward compatibility test** - Specific test mentioned in Task 12 plan
|
|
||||||
|
|
||||||
### Documentation Gaps
|
|
||||||
|
|
||||||
1. README.md is in German but doesn't mention multi-algorithm support yet
|
|
||||||
2. CLAUDE.md is German translation but may be outdated vs English version
|
|
||||||
3. No API docs for algorithm developers
|
|
||||||
|
|
||||||
### Potential Improvements
|
|
||||||
|
|
||||||
1. Add `list-algorithms` subcommand to CLIs
|
|
||||||
2. Complete algorithm support in `salt2.py`
|
|
||||||
3. Add integration tests with subprocess
|
|
||||||
4. Document algorithm addition process more clearly
|
|
||||||
5. Consider packaging as proper Python package (`salt/` directory structure)
|
|
||||||
|
|
||||||
## Working with Plans and Documentation
|
## Working with Plans and Documentation
|
||||||
|
|
||||||
### Plan Execution Pattern
|
### Plan Execution Pattern
|
||||||
|
|
||||||
The repository includes a detailed plan at `docs/plans/2025-11-13-multi-algorithm-support.md`:
|
The repository includes a detailed plan at `docs/plans/2025-11-13-multi-algorithm-support.md`. When implementing from plans, follow the TDD approach outlined in the tasks.
|
||||||
- 12 tasks with step-by-step TDD approach
|
|
||||||
- Each task has verification steps
|
|
||||||
- Plan includes commit message examples
|
|
||||||
- Some tasks completed, others not yet
|
|
||||||
|
|
||||||
**If implementing from plans:**
|
|
||||||
1. Read the full task before starting
|
|
||||||
2. Follow TDD: failing test first, then implementation
|
|
||||||
3. Run tests after each step
|
|
||||||
4. Use suggested commit messages
|
|
||||||
5. Mark off checklist items as you complete them
|
|
||||||
|
|
||||||
### Multiple Documentation Files
|
### Multiple Documentation Files
|
||||||
|
|
||||||
- **AGENTS.md**: This file - agent/developer guidelines
|
- **AGENTS.md**: This file - agent/developer guidelines
|
||||||
- **CLAUDE.md**: German translation, more detailed code examples
|
- **CLAUDE.md**: German translation, more detailed code examples
|
||||||
- **README.md**: User-facing documentation in German
|
- **README.md**: User-facing documentation in German
|
||||||
- **docs/plans/*.md**: Implementation plans with TDD steps
|
- **docs/plans/*.md**: Implementation plans with TDD steps
|
||||||
|
|
||||||
Keep these in sync when making significant changes.
|
Keep these in sync when making significant changes.
|
||||||
|
|
||||||
## Quick Reference
|
|
||||||
|
|
||||||
### File a Bug or Add a Feature
|
|
||||||
|
|
||||||
1. Create test demonstrating issue/feature
|
|
||||||
2. Implement fix/feature
|
|
||||||
3. Run `python3 -m pytest -v`
|
|
||||||
4. Commit with conventional message
|
|
||||||
5. Update docs if needed
|
|
||||||
|
|
||||||
### Debug a Test Failure
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run specific test with verbose output
|
|
||||||
python3 -m pytest tests/test_file.py::test_name -vv
|
|
||||||
|
|
||||||
# Run with pdb on failure
|
|
||||||
python3 -m pytest --pdb tests/test_file.py::test_name
|
|
||||||
|
|
||||||
# See print statements
|
|
||||||
python3 -m pytest -s tests/test_file.py
|
|
||||||
```
|
|
||||||
|
|
||||||
### Verify All Systems
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Comprehensive check
|
|
||||||
python3 -m pytest -v && \
|
|
||||||
python3 salt.py "test" && \
|
|
||||||
python3 salt.py hash --algorithm argon2 "test" && \
|
|
||||||
python3 salt.py hash --algorithm bcrypt "test" && \
|
|
||||||
python3 salt2.py generate "test" && \
|
|
||||||
echo "All systems operational"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Document Version:** 2025-11-13
|
|
||||||
**Last Updated:** Initial comprehensive analysis based on repository state
|
|
||||||
**Test Count:** 20 tests (all passing)
|
|
||||||
**Python Version:** 3.12.3
|
|
||||||
|
|||||||
Binary file not shown.
BIN
__pycache__/salt2.cpython-312.pyc
Normal file
BIN
__pycache__/salt2.cpython-312.pyc
Normal file
Binary file not shown.
15
salt.py
15
salt.py
@@ -78,6 +78,11 @@ def _build_parser() -> argparse.ArgumentParser:
|
|||||||
default="pbkdf2",
|
default="pbkdf2",
|
||||||
help="Hash-Algorithmus (Standard: pbkdf2)",
|
help="Hash-Algorithmus (Standard: pbkdf2)",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
subparsers.add_parser(
|
||||||
|
"list-algorithms", help="Zeigt alle verfügbaren Hash-Algorithmen an."
|
||||||
|
)
|
||||||
|
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
@@ -86,7 +91,7 @@ def _normalize_args(argv: Sequence[str] | None) -> list[str]:
|
|||||||
if not argv:
|
if not argv:
|
||||||
return []
|
return []
|
||||||
# If it starts with a subcommand, leave as-is
|
# If it starts with a subcommand, leave as-is
|
||||||
if argv[0] in {"hash", "verify"}:
|
if argv[0] in {"hash", "verify", "list-algorithms"}:
|
||||||
return list(argv)
|
return list(argv)
|
||||||
# If it starts with help flags, leave as-is
|
# If it starts with help flags, leave as-is
|
||||||
if argv[0] in {"-h", "--help"}:
|
if argv[0] in {"-h", "--help"}:
|
||||||
@@ -104,6 +109,14 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||||||
arg_list = list(argv) if argv is not None else sys.argv[1:]
|
arg_list = list(argv) if argv is not None else sys.argv[1:]
|
||||||
args = parser.parse_args(_normalize_args(arg_list))
|
args = parser.parse_args(_normalize_args(arg_list))
|
||||||
|
|
||||||
|
if args.command == "list-algorithms":
|
||||||
|
from algorithms import list_algorithms
|
||||||
|
|
||||||
|
print("Verfügbare Algorithmen:")
|
||||||
|
for algo in list_algorithms():
|
||||||
|
print(f" - {algo}")
|
||||||
|
return 0
|
||||||
|
|
||||||
if args.command == "verify":
|
if args.command == "verify":
|
||||||
if verify_password(args.password, args.salt, args.hash, algorithm=args.algorithm):
|
if verify_password(args.password, args.salt, args.hash, algorithm=args.algorithm):
|
||||||
print("✓ Passwort korrekt")
|
print("✓ Passwort korrekt")
|
||||||
|
|||||||
36
salt2.py
36
salt2.py
@@ -10,6 +10,8 @@ from salt import hash_password, verify_password
|
|||||||
|
|
||||||
|
|
||||||
def _build_parser() -> argparse.ArgumentParser:
|
def _build_parser() -> argparse.ArgumentParser:
|
||||||
|
from algorithms import list_algorithms
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="CLI für Password-Hashing (PBKDF2, Argon2, bcrypt) inklusive Verify-Modus."
|
description="CLI für Password-Hashing (PBKDF2, Argon2, bcrypt) inklusive Verify-Modus."
|
||||||
)
|
)
|
||||||
@@ -19,6 +21,13 @@ def _build_parser() -> argparse.ArgumentParser:
|
|||||||
"generate", help="Erzeugt ein neues Salt+Hash Paar."
|
"generate", help="Erzeugt ein neues Salt+Hash Paar."
|
||||||
)
|
)
|
||||||
generate_parser.add_argument("password", help="Klartext-Passwort zum Hashen.")
|
generate_parser.add_argument("password", help="Klartext-Passwort zum Hashen.")
|
||||||
|
generate_parser.add_argument(
|
||||||
|
"--algorithm",
|
||||||
|
"-a",
|
||||||
|
choices=list_algorithms(),
|
||||||
|
default="pbkdf2",
|
||||||
|
help="Hash-Algorithmus (Standard: pbkdf2)",
|
||||||
|
)
|
||||||
|
|
||||||
verify_parser = subparsers.add_parser(
|
verify_parser = subparsers.add_parser(
|
||||||
"verify",
|
"verify",
|
||||||
@@ -27,29 +36,52 @@ def _build_parser() -> argparse.ArgumentParser:
|
|||||||
verify_parser.add_argument("password", help="Passwort zum Prüfen.")
|
verify_parser.add_argument("password", help="Passwort zum Prüfen.")
|
||||||
verify_parser.add_argument("salt", help="Base64-kodiertes Salt.")
|
verify_parser.add_argument("salt", help="Base64-kodiertes Salt.")
|
||||||
verify_parser.add_argument("hash", help="Base64-kodierter Hash.")
|
verify_parser.add_argument("hash", help="Base64-kodierter Hash.")
|
||||||
|
verify_parser.add_argument(
|
||||||
|
"--algorithm",
|
||||||
|
"-a",
|
||||||
|
choices=list_algorithms(),
|
||||||
|
default="pbkdf2",
|
||||||
|
help="Hash-Algorithmus (Standard: pbkdf2)",
|
||||||
|
)
|
||||||
|
|
||||||
|
subparsers.add_parser(
|
||||||
|
"list-algorithms", help="Zeigt alle verfügbaren Hash-Algorithmen an."
|
||||||
|
)
|
||||||
|
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
def _command_generate(args: argparse.Namespace) -> int:
|
def _command_generate(args: argparse.Namespace) -> int:
|
||||||
salt, hash_value = hash_password(args.password)
|
salt, hash_value = hash_password(args.password, algorithm=args.algorithm)
|
||||||
print(f"Salt: {salt}")
|
print(f"Salt: {salt}")
|
||||||
print(f"Hash: {hash_value}")
|
print(f"Hash: {hash_value}")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def _command_verify(args: argparse.Namespace) -> int:
|
def _command_verify(args: argparse.Namespace) -> int:
|
||||||
if verify_password(args.password, args.salt, args.hash):
|
if verify_password(args.password, args.salt, args.hash, algorithm=args.algorithm):
|
||||||
print("✓ Passwort korrekt")
|
print("✓ Passwort korrekt")
|
||||||
return 0
|
return 0
|
||||||
print("✗ Passwort falsch")
|
print("✗ Passwort falsch")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
def _command_list_algorithms() -> int:
|
||||||
|
from algorithms import list_algorithms
|
||||||
|
|
||||||
|
print("Verfügbare Algorithmen:")
|
||||||
|
for algo in list_algorithms():
|
||||||
|
print(f" - {algo}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
def main(argv: Sequence[str] | None = None) -> int:
|
||||||
parser = _build_parser()
|
parser = _build_parser()
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
if args.command == "generate":
|
if args.command == "generate":
|
||||||
return _command_generate(args)
|
return _command_generate(args)
|
||||||
|
if args.command == "list-algorithms":
|
||||||
|
return _command_list_algorithms()
|
||||||
return _command_verify(args)
|
return _command_verify(args)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
BIN
tests/__pycache__/test_cli2.cpython-312-pytest-9.0.1.pyc
Normal file
BIN
tests/__pycache__/test_cli2.cpython-312-pytest-9.0.1.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
tests/__pycache__/test_integration.cpython-312-pytest-9.0.1.pyc
Normal file
BIN
tests/__pycache__/test_integration.cpython-312-pytest-9.0.1.pyc
Normal file
Binary file not shown.
18
tests/test_cli2.py
Normal file
18
tests/test_cli2.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
from salt import hash_password
|
||||||
|
from salt2 import main
|
||||||
|
|
||||||
|
|
||||||
|
def test_main_generate_with_algorithm_flag():
|
||||||
|
"""Verify salt2 CLI accepts --algorithm flag."""
|
||||||
|
assert main(["generate", "--algorithm", "pbkdf2", "secret"]) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_main_verify_with_algorithm_flag():
|
||||||
|
"""Verify salt2 CLI verify accepts --algorithm flag."""
|
||||||
|
salt, hashed = hash_password("secret", algorithm="pbkdf2")
|
||||||
|
assert main(["verify", "--algorithm", "pbkdf2", "secret", salt, hashed]) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_main_list_algorithms_command():
|
||||||
|
"""Verify salt2 CLI has list-algorithms command."""
|
||||||
|
assert main(["list-algorithms"]) == 0
|
||||||
@@ -24,3 +24,16 @@ def test_hash_password_with_algorithm_parameter():
|
|||||||
"""Verify hash_password accepts algorithm parameter."""
|
"""Verify hash_password accepts algorithm parameter."""
|
||||||
salt, hashed = hash_password("test", algorithm="pbkdf2")
|
salt, hashed = hash_password("test", algorithm="pbkdf2")
|
||||||
assert verify_password("test", salt, hashed, algorithm="pbkdf2")
|
assert verify_password("test", salt, hashed, algorithm="pbkdf2")
|
||||||
|
|
||||||
|
|
||||||
|
def test_backward_compatibility_with_old_pbkdf2_hashes():
|
||||||
|
"""Verify existing PBKDF2 hashes still work without algorithm parameter."""
|
||||||
|
# Simulate old hash created before algorithm parameter existed
|
||||||
|
salt, hashed = hash_password("legacy-password")
|
||||||
|
|
||||||
|
# Verify using old API (no algorithm parameter)
|
||||||
|
assert verify_password("legacy-password", salt, hashed)
|
||||||
|
assert not verify_password("wrong", salt, hashed)
|
||||||
|
|
||||||
|
# Verify using new API with explicit pbkdf2
|
||||||
|
assert verify_password("legacy-password", salt, hashed, algorithm="pbkdf2")
|
||||||
|
|||||||
51
tests/test_integration.py
Normal file
51
tests/test_integration.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
"""Integration tests for multi-algorithm password hashing."""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def test_pbkdf2_cli_integration():
|
||||||
|
"""Test PBKDF2 end-to-end via CLI."""
|
||||||
|
result = subprocess.run(
|
||||||
|
[sys.executable, "salt.py", "hash", "--algorithm", "pbkdf2", "test123"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
assert result.returncode == 0
|
||||||
|
assert "Salt:" in result.stdout
|
||||||
|
assert "Hash:" in result.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def test_argon2_cli_integration():
|
||||||
|
"""Test Argon2 end-to-end via CLI."""
|
||||||
|
result = subprocess.run(
|
||||||
|
[sys.executable, "salt.py", "hash", "--algorithm", "argon2", "test123"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
assert result.returncode == 0
|
||||||
|
assert "Hash:" in result.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def test_bcrypt_cli_integration():
|
||||||
|
"""Test bcrypt end-to-end via CLI."""
|
||||||
|
result = subprocess.run(
|
||||||
|
[sys.executable, "salt.py", "hash", "--algorithm", "bcrypt", "test123"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
assert result.returncode == 0
|
||||||
|
assert "Hash:" in result.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def test_list_algorithms_integration():
|
||||||
|
"""Test list-algorithms command."""
|
||||||
|
result = subprocess.run(
|
||||||
|
[sys.executable, "salt.py", "list-algorithms"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
assert result.returncode == 0
|
||||||
|
assert "argon2" in result.stdout
|
||||||
|
assert "bcrypt" in result.stdout
|
||||||
|
assert "pbkdf2" in result.stdout
|
||||||
Reference in New Issue
Block a user