diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d0ee3b1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +venv/ +__pycache__/ +*.pyc diff --git a/app.py b/app.py index 88fdab1..7c1dffb 100644 --- a/app.py +++ b/app.py @@ -1,16 +1,17 @@ from flask import Flask, request, jsonify, render_template, abort -from passlib.hash import sha512_crypt, sha256_crypt, md5_crypt -import secrets, os +import secrets, crypt, os app = Flask(__name__, static_folder='static', template_folder='templates') SALT_CHARS = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" MIN_LEN = 16 +MIN_SALT_LEN = 8 MAX_SALT_LEN = 16 -ALG_MAP = { - 'sha512': sha512_crypt, - 'sha256': sha256_crypt + +ALG_PREFIX = { + 'sha512': '$6$', + 'sha256': '$5$', } @app.route('/') @@ -19,9 +20,11 @@ def index(): @app.route('/gensalt') def gensalt(): - salt = ''.join(secrets.choice(SALT_CHARS) for _ in range(MAX_SALT_LEN)) + length = max(MIN_SALT_LEN, min(MAX_SALT_LEN, int(request.args.get('length', MIN_SALT_LEN)))) + salt = ''.join(secrets.choice(SALT_CHARS) for _ in range(length)) return jsonify({'salt': salt}) + @app.route('/hash', methods=['POST']) def do_hash(): data = request.get_json() or {} @@ -29,23 +32,25 @@ def do_hash(): salt = data.get('salt', '') algorithm = data.get('algorithm', 'sha512') + if not isinstance(password, str) or not isinstance(salt, str): abort(400, 'Invalid input') if len(password) < MIN_LEN: abort(400, f'Password must be at least {MIN_LEN} characters') - if len(salt) < MIN_LEN or len(salt) > MAX_SALT_LEN: - abort(400, f'Salt must be between {MIN_LEN} and {MAX_SALT_LEN} characters') + if len(salt) < MIN_SALT_LEN or len(salt) > MAX_SALT_LEN: + abort(400, f'Salt must be between {MIN_SALT_LEN} and {MAX_SALT_LEN} characters') - hash_class = ALG_MAP.get(algorithm) - if hash_class is None: + prefix = ALG_PREFIX.get(algorithm) + + if prefix is None: abort(400, 'Unsupported algorithm') - # truncate salt to MAX_SALT_LEN just in case - salt_to_use = salt[:MAX_SALT_LEN] - hashed = hash_class.using(salt=salt_to_use).hash(password) + full_salt = f"{prefix}{salt}" + hashed = crypt.crypt(password, full_salt) return jsonify({'hash': hashed}) + if __name__ == '__main__': host = os.environ.get('HOST', '127.0.0.1') port = int(os.environ.get('PORT', 4444)) diff --git a/style.css b/static/css/style.css similarity index 88% rename from style.css rename to static/css/style.css index cc5fa17..693d0fd 100644 --- a/style.css +++ b/static/css/style.css @@ -36,6 +36,10 @@ button { color: white } +button:hover { + cursor: pointer; +} + .row { display: flex; gap: 8px @@ -58,4 +62,4 @@ button { display: flex; gap: 8px; margin-top: 12px -} \ No newline at end of file +} diff --git a/main.js b/static/js/main.js similarity index 70% rename from main.js rename to static/js/main.js index 1d0b24f..3bbdaae 100644 --- a/main.js +++ b/static/js/main.js @@ -6,44 +6,51 @@ const hashBtn = document.getElementById('hashBtn'); const result = document.getElementById('result'); const clearBtn = document.getElementById('clearBtn'); -const MIN_LEN = 16; +const MIN_PASS_LEN = 16; +const MIN_SALT_LEN = 8; +const MAX_SALT_LEN = 16; gensaltBtn.addEventListener('click', async () => { - const len = Math.max(MIN_LEN, MIN_LEN); + const len = MAX_SALT_LEN; // can be adjusted or user-defined const res = await fetch('/gensalt?length=' + len); if (!res.ok) { alert('Could not generate salt'); return; } const data = await res.json(); salt.value = data.salt; }); - hashBtn.addEventListener('click', async () => { const pass = password.value || ''; const s = salt.value || ''; const alg = algorithm.value; - - if (pass.length < MIN_LEN) { alert('Password must be at least ' + MIN_LEN + ' characters'); return; } - - if (s.length < MIN_LEN) { alert('Salt must be at least ' + MIN_LEN + ' characters'); return; } - + + if (pass.length < MIN_PASS_LEN) { + alert('Password must be at least ' + MIN_PASS_LEN + ' characters'); + return; + } + + if (s.length < MIN_SALT_LEN || s.length > MAX_SALT_LEN) { + alert('Salt must be between ' + MIN_SALT_LEN + ' and ' + MAX_SALT_LEN + ' characters'); + return; + } + const payload = { password: pass, salt: s, algorithm: alg }; const res = await fetch('/hash', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); - + if (!res.ok) { const txt = await res.text(); alert('Error: ' + txt); return; } - + const data = await res.json(); result.value = data.hash; }); - clearBtn.addEventListener('click', () => { password.value = ''; salt.value = ''; result.value = ''; -}); \ No newline at end of file +}); + diff --git a/index.html b/templates/index.html similarity index 91% rename from index.html rename to templates/index.html index aacfc39..600408c 100644 --- a/index.html +++ b/templates/index.html @@ -23,7 +23,7 @@ - +