102 lines
3.0 KiB
Python
102 lines
3.0 KiB
Python
from flask import Flask, request, jsonify, render_template, abort
|
|
from argon2 import PasswordHasher
|
|
from argon2.low_level import Type as ArgonType, hash_secret
|
|
import secrets, os
|
|
|
|
try:
|
|
import crypt
|
|
except ImportError:
|
|
import crypt_r as crypt
|
|
|
|
app = Flask(__name__, static_folder='static', template_folder='templates')
|
|
|
|
SALT_CHARS = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
|
MIN_LEN = 16
|
|
MIN_SALT_LEN = 8
|
|
MAX_SALT_LEN = 16
|
|
|
|
ph = PasswordHasher()
|
|
|
|
ALG_PREFIX = {
|
|
'sha512': '$6$',
|
|
'sha256': '$5$',
|
|
'argon2_std': '$argon2id$',
|
|
'argon2_copyparty': '+'
|
|
}
|
|
|
|
# The main route
|
|
@app.route('/')
|
|
def index():
|
|
return render_template('index.html')
|
|
|
|
# This is what generates salt
|
|
@app.route('/gensalt')
|
|
def gensalt():
|
|
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 {}
|
|
username = data.get('username', '')
|
|
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 algorithm == 'argon2_copyparty':
|
|
if (username == '' or username == None):
|
|
abort(400, 'Please type your username.')
|
|
|
|
specified_salt = 'LVZ1TJMdAIdLyBla6nWDexFt'
|
|
b_pass = password.encode('utf-8')
|
|
b_salt = specified_salt.encode('utf-8')
|
|
|
|
raw_hash_copyparty = hash_secret(
|
|
secret = b_pass,
|
|
salt = b_salt,
|
|
time_cost = 3,
|
|
memory_cost = 256 * 1024,
|
|
parallelism = 4,
|
|
hash_len = 24,
|
|
type = ArgonType.ID,
|
|
version = 19
|
|
)
|
|
|
|
hash_only = raw_hash_copyparty.split(b"$")[-1].decode('utf-8')
|
|
final_hash = username + ":+" + hash_only.replace('/', "_").replace('+', '-')
|
|
|
|
return jsonify({'hash': final_hash})
|
|
|
|
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')
|
|
|
|
if algorithm == 'argon2_std':
|
|
hashed = ph.hash(password, salt = salt.encode('utf-8'))
|
|
return jsonify({'hash': hashed})
|
|
|
|
prefix = ALG_PREFIX.get(algorithm)
|
|
|
|
if prefix is None:
|
|
abort(400, 'Unsupported algorithm')
|
|
|
|
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))
|
|
debug = os.environ.get('DEBUG', '1') == '1'
|
|
app.run(host=host, port=port, debug=debug)
|
|
|