From f5aabf7f2abc5d55c1eeb334af12b518fc6a50f4 Mon Sep 17 00:00:00 2001 From: Eri Date: Sun, 12 Oct 2025 01:42:28 +0200 Subject: [PATCH] init commit Initial commit with all of the files. --- app.py | 53 ++++++++++++++++++++++++++++++++++++++++ index.html | 47 ++++++++++++++++++++++++++++++++++++ main.js | 49 +++++++++++++++++++++++++++++++++++++ requirements.txt | Bin 0 -> 300 bytes style.css | 61 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 210 insertions(+) create mode 100644 app.py create mode 100644 index.html create mode 100644 main.js create mode 100644 requirements.txt create mode 100644 style.css diff --git a/app.py b/app.py new file mode 100644 index 0000000..88fdab1 --- /dev/null +++ b/app.py @@ -0,0 +1,53 @@ +from flask import Flask, request, jsonify, render_template, abort +from passlib.hash import sha512_crypt, sha256_crypt, md5_crypt +import secrets, os + +app = Flask(__name__, static_folder='static', template_folder='templates') + +SALT_CHARS = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +MIN_LEN = 16 +MAX_SALT_LEN = 16 + +ALG_MAP = { + 'sha512': sha512_crypt, + 'sha256': sha256_crypt +} + +@app.route('/') +def index(): + return render_template('index.html') + +@app.route('/gensalt') +def gensalt(): + salt = ''.join(secrets.choice(SALT_CHARS) for _ in range(MAX_SALT_LEN)) + return jsonify({'salt': salt}) + +@app.route('/hash', methods=['POST']) +def do_hash(): + data = request.get_json() or {} + password = data.get('password', '') + 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') + + hash_class = ALG_MAP.get(algorithm) + if hash_class 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) + return jsonify({'hash': hashed}) + +if __name__ == '__main__': + host = os.environ.get('HOST', '127.0.0.1') + port = int(os.environ.get('PORT', 4444)) + debug = os.environ.get('DEBUG', '1') == '1' + app.run(host=host, port=port, debug=debug) diff --git a/index.html b/index.html new file mode 100644 index 0000000..aacfc39 --- /dev/null +++ b/index.html @@ -0,0 +1,47 @@ + + + + + + + TNC's Hashing Tool + + + + +
+

TNC's Hashing Tool

+ + + + + + + + + + +
+ + +
+
Salt characters limited to ./0-9A-Za-z.
+ + +
+ + +
+ + + + +
+ + + + + \ No newline at end of file diff --git a/main.js b/main.js new file mode 100644 index 0000000..1d0b24f --- /dev/null +++ b/main.js @@ -0,0 +1,49 @@ +const gensaltBtn = document.getElementById('gensaltBtn'); +const algorithm = document.getElementById('algorithm'); +const password = document.getElementById('password'); +const salt = document.getElementById('salt'); +const hashBtn = document.getElementById('hashBtn'); +const result = document.getElementById('result'); +const clearBtn = document.getElementById('clearBtn'); + +const MIN_LEN = 16; + +gensaltBtn.addEventListener('click', async () => { + const len = Math.max(MIN_LEN, MIN_LEN); + 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; } + + 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/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..ca84604449f8748a218a746e82af9f3fdc1f034a GIT binary patch literal 300 zcmYk1I}gGz41{k+;;*0*DjowXLnS5#=C(Xk3Qg6Lg&z;xIYdI1mB^ocK7Za7?r6}V z#Tf&(j5%i5Ff&XsL2bMy7hAKw_+y^bbFRdLsg8^^XD)4eM_yw5O>0u>&RByd$%(wf z)wX*CR;1#rD|sl);f* { + flex: 1 +} + +.small { + width: 140px +} + +.note { + font-size: 0.9em; + color: #ffffff +} + +.controls { + display: flex; + gap: 8px; + margin-top: 12px +} \ No newline at end of file