Most secure way of logging in for my project

Hey guys! I’m trying to create a forum of my own (because I couldn’t get Discourse to setup without Docker, so I’m making one myself). The part which I’m on is the login system, and I want to ask you guys which is the safest and most secure option to store the email/username/password to login.


  1. Use hashes. This method is very secure but has 1 major flaw: it’s random everytime, so I can’t check against the data (posts etc)
  2. Use a cipher that I made, then cipher the ciphered login data. A third cipher can be used (I just need an algorithm to undo the ciphers). The flaw: I know how to crack it, potentially making me log into your account. But I wouldn’t do that.
  3. Directly store the login data, so I can see the login data just like that. No security whatsoever.
  4. Just came across this one today: use Replit Auth, then use the web module from replit to get the person who logged in. This is the safest because I can store it as a variable, then maybe put it in some database idk.

Which is the best option (votes are anonymous)? If none, please tell me a safer option to store the data.


  • Option 1
  • Option 2
  • Option 3
  • Option 4
  • Other (PLEASE COMMENT)
0 voters

Note that all the options have flaws, so a foolproof method has yet to come up.

Btw, I’m using Python with Flask.

1 Like

1.

Absolutely not 2. Seriously please don’t use anything you make yourself lol.

Absolutely not 3! This is obvious why.

Why not 4? Not everyone has a Replit account.

I’m making my own forum thing myself (but making like a million other projects at once as well, so that’s never getting finished), I just hashed and salted passwords, and stored them in Replit DB. Not sure if that’s the most secure :woman_shrugging:

from random import randint


def pad(string: str, length: int, character: str = "0") -> str:
    padded_string = string
    while len(padded_string) < length:
        padded_string += character
    return padded_string


def hash_and_salt(password: str) -> dict[str: str, str: str]:
    salt = pad(str(randint(0, 99999999)), 8)
    return {"password": hash(password) + salt, "salt": salt}
2 Likes

yes, the reason why people use hashes is because they can’t be reversed. Also consider storing them in a neon db outside of replit (for added security, bc neon’s main focus is db making it less susceptible to vulnerabilities and problems).


real life example: pychat.python660.repl.co

Signups are at replit.com/@python660/Firebase-Login-System-DEVELOPMENT
with verification required on my end + secrets, but the gist is there.

3 Likes

psst…
don’t use python’s string hash for passwords!

Why? Some python builtin hashes have per-interpreter salt themselves, so if you reset your program, the passwords are probably lost forever :upside_down_face:.

On a side note, python has builtin padding, e.g. str.ljust or f'{n:08}'

Fixed:

from random import randbytes
from hashlib import sha256

def hash_and_salt(password: str) -> tuple[bytes, bytes]:
    salt = randbytes(8)
    return sha256(password.encode()+salt).digest(), salt
2 Likes

Option 1, salt and hash is the only good way of storing passwords (other than delegating it to a third party). Only the password needs to be hashed.
The email and username should probably just be encrypted (option 2).

3 Likes

well you don’t need to encrypt username bc why? what about id.


Don’t use sha256!!! use bcrypt, whatever it was, because sha256 is specifically designed to be fast, something you dont want to have in passwords!

3 Likes

Option 1 is the safest, but keep in mind that you only need to hash passwords, not usernames or emails.

5 Likes

Is the hash random everytime?

As far as I know, hash functions with the same inputs return the same result. If they weren’t, they wouldn’t be of much use.

6 Likes

I just make my own hashes:

package main

import (
    "encoding/binary"
    "encoding/hex"
    "fmt"
)

type HashGenerator struct{}

func (h *HashGenerator) generateHash(data []byte) string {
    return h.hashFunction(data)
}

func (h *HashGenerator) hashFunction(data []byte) string {
    var hh = [8]uint32{
        1779033703, 3144134277, 1013904242, 2773480762,
        1359893119, 2600822924, 528734635, 1541459225,
    }

    var k = [64]uint32{
        1116352408, 1899447441, 3049323471, 3921009573, 961987163, 1508970993,
        2453635748, 2870763221, 3624381080, 310598401, 607225278, 1426881987,
        1925078388, 2162078206, 2614888103, 3248222580, 3835390401, 4022224774,
        264347078, 604807628, 770255983, 1249150122, 1555081692, 1996064986,
        2554220882, 2821834349, 2952996808, 3210313671, 3336571891, 3584528711,
        113926993, 338241895, 666307205, 773529912, 1294757372, 1396182291,
        1695183700, 1986661051, 2177026350, 2456956037, 2730485921, 2820302411,
        3259730800, 3345764771, 3516065817, 3600352804, 4094571909, 275423344,
        430227734, 506948616, 659060556, 883997877, 958139571, 1322822218,
        1537002063, 1747873779, 1955562222, 2024104815, 2227730452, 2361852424,
        2428436474, 2756734187, 3204031479, 3329325298,
    }

    padding := make([]byte, 64-len(data)%64)
    padding[0] = 0x80
    data = append(data, padding...)
    data = append(data, make([]byte, 8)...)
    binary.BigEndian.PutUint64(data[len(data)-8:], uint64(len(data)-len(padding)-8)*8)

    var w [64]uint32

    for i := 0; i < len(data); i += 64 {
        for j := 0; j < 16; j++ {
            w[j] = binary.BigEndian.Uint32(data[i+j*4 : i+j*4+4])
        }

        for j := 16; j < 64; j++ {
            s0 := (w[j-15]>>7 | w[j-15]<<(32-7)) ^ (w[j-15]>>18 | w[j-15]<<(32-18)) ^ (w[j-15] >> 3)
            s1 := (w[j-2]>>17 | w[j-2]<<(32-17)) ^ (w[j-2]>>19 | w[j-2]<<(32-19)) ^ (w[j-2] >> 10)
            w[j] = w[j-16] + s0 + w[j-7] + s1
        }

        a, b, c, d, e, f, g, h0 := hh[0], hh[1], hh[2], hh[3], hh[4], hh[5], hh[6], hh[7]

        for j := 0; j < 64; j++ {
            s1 := (e>>6 | e<<(32-6)) ^ (e>>11 | e<<(32-11)) ^ (e>>25 | e<<(32-25))
            ch := (e & f) ^ (^e & g)
            temp1 := h0 + s1 + ch + k[j] + w[j]
            s0 := (a>>2 | a<<(32-2)) ^ (a>>13 | a<<(32-13)) ^ (a>>22 | a<<(32-22))
            maj := (a & b) ^ (a & c) ^ (b & c)
            temp2 := s0 + maj

            h0 = g
            g = f
            f = e
            e = d + temp1
            d = c
            c = b
            b = a
            a = temp1 + temp2
        }

        hh[0] += a
        hh[1] += b
        hh[2] += c
        hh[3] += d
        hh[4] += e
        hh[5] += f
        hh[6] += g
        hh[7] += h0
    }

    var hash [32]byte
    for i, val := range hh {
        binary.BigEndian.PutUint32(hash[i*4:i*4+4], val)
    }

    return hex.EncodeToString(hash[:])
}

func verifyHash(data []byte, expectedHash string) bool {
    hg := HashGenerator{}
    actualHash := hg.generateHash(data)
    return expectedHash == actualHash
}

func main() {
    hg := HashGenerator{}
    data := "brrr @sky."
    hash := hg.generateHash([]byte(data))
    fmt.Println("Hash:", hash)

    valid := verifyHash([]byte(data), hash)
    fmt.Println("Valid:", valid)
}
2 Likes

Personally… what’s the 1116352408, 1899447441, 3049323471, 3921009573, 961987163, 1508970993, 2453635748, 2870763221, 3624381080, 310598401, 607225278, 1426881987, 1925078388, 2162078206, 2614888103, 3248222580, 3835390401, 4022224774, 264347078, 604807628, 770255983, 1249150122, 1555081692, 1996064986, 2554220882, 2821834349, 2952996808, 3210313671, 3336571891, 3584528711, 113926993, 338241895, 666307205, 773529912, 1294757372, 1396182291, 1695183700, 1986661051, 2177026350, about?

3 Likes

For logging in, use bcrypt to hash data. If you need to store sensitive data along with the account, use the SHA256/MD5 (remember, use bcrypt to hash, these functions are irrelevant) of the password as an encryption key for a symmetric algorithm (use AES). Using a third party login like repl auth is also a good practise

A hash function scrambles data in such a way that it can never be recovered. It does the same thing each time, which means that it is useful for password “storage” (it isn’t really storage). You want a slow hash function so that it takes impossibly long to crack and you should have a salt so that you can’t pre-process passwords (a salt is a random string added to a password before encryption). This is why I suggest using bcrypt

5 Likes

Not if you give it identical parameters. In python, I’ve heard that the hash function varies bc it generates a salt for you. However, as long as you can set the salt, (similar to pepper lol), iterations, algorithm, and recursion number (how many times it passes the data through the algorithm), you should end up with identical data.

3 Likes

It’s the constants k used in the SHA-256 algorithm for bitwise operations during the hash computation.

3 Likes

if I were making something like this, I’d probably use third party authentication to avoid all of these problems

3 Likes

That’s what option 4 is, Replit Auth.

1 Like

Ah, I forgot about that option

1 Like

Ok so I’ve been thinking, and I realised the best way to do this is to use Replit Auth to login. No other option.
You can’t hack it unless you hack the user’s Replit account, so it’s pretty safe.

1 Like

…?

You made a poll, and we answered the poll, where the majority chose option 1.

Why use option 4?

2 Likes