I am implementing a magic link/passwordless authentication.
I am sending an email with a token generated via crypto.randomBytes, when the user clicks on the link, it is redirected to the app and the token is validated to make sure it is unique.
Does the number of bytes matter, and if yes what would be a good number?
token is validated to make sure it is unique
maybe you could as well validate that it's not yet expired (define some validity to the token)
Does the number of bytes matter, and if yes what would be a good number?
In security, size does matter. It is considered as unfeasible to guess if the random output is 128 bit long (=16 bytes), or 256 bit (=32 bytes) with safe margin.
As well you may add some integrity/authentication check, such as signature or hmac, if you use simple random number generator (not from any serious crypto library) or counter
Related
I am reading bep 5 and trying to understand how a token value is generated. As I understand the token value is a randomly generated value that is used in a get_peers query for safety. This same token value would then be used in an announced_peers query to see if the same IP previously requested the same Infohash.
My question is how is this value generated exactly? It says something about an unspecified implementation - does this mean I can implement it myself (for example by using the SHA-1 value)?
I tried looking at other beps but couldn't find anything about specific rules for generating a token value, found nothing.
The token represents a write permission so that the other node may follow up with an announce request carrying that write permission.
Since the write permission is specific to an individual node providing the token it is not necessary to specify how it keeps track of valid write permissions, as there needs to be no agreement between nodes how the implementation works. For everyone else the token is just an opaque sequence of bytes, essentially a key.
Possible implementations are
(stateful) keep a hashmap mapping from supplied tokens to their expiration time and which remote IP it is valid for.
(stateless) hash a secret the remote ip, remote id and a validity-time-window-counter. then truncate the hash. bump the counter on a timer. when verifying check with the current and the previous counter.
Since a token is only valid for a few minutes and a node should also have a spam throttle it doesn't need to be high strength, just enough bits to make it impossible to brute-force. 6-8 bytes is generally enough for that purpose.
The underlying goal is to hand out a space-efficient, time-limited write permission to individual nodes in a way that other nodes can't forge.
I am generating a session key to be stored in a cookie using the following function:
function getRandomKey($length=32) {
$string = '';
$characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
for ($i = 0; $i < $length; $i++) {
$string .= $characters[mt_rand(0, strlen($characters)-1)];
}
return $string;
}
If I were to generate a 1 digit key it would have:
26 lowercase + 26 uppercase + 0-10 = 62 options.
Therefore an 8 digit key would have 62^8 or 218,340,105,584,896 possible combinations.
1) Is there any rule of thumb on how many characters out I should go? The more the better, I know, but is 8 enough or should it be more like 32 characters, 64 etc.?
2) Are there any security concerns when using localStorage?
Thanks in advance!
These are two very different questions.
1) TL;DR: about 16 characters (case-sensitive) is ok for most purposes.
First, please if you can, avoid implementing session management. It is already done in many frameworks, including session id generation and more - use an existing, well-known implementation if you can, because it is not straightforward to get it right.
Now, it's all about entropy. You started out right by calculating the number of possible combinations. If you take log2 of that, you get how many bits of entropy that session id has. (Well, let's not go into entropy here...)
So one case-sensitive alphanumeric character ([a-zA-Z0-9]) has log2(62)=5.9542 bits of entropy, two characters two times more, and so on.
The time required for an attacker to guess a valid session id is:
(2^b + 1) / (2 * n * s)
Where 'b' is the available bits of entropy in the session id, 'n' is the number of guesses the attacker can make every second, and 's' is the number of valid session ids in the system.
In a large, distributed web application, potentially using a botnet, an attacker may be able to make n=100000 guesses a second, and there may be s=1 million valid session ids. You want the result to be several hundred years at the very least, say 300 (15768000000 seconds). (These are totally arbitrary values.)
This gives about b=70, so you need 70 bits of entropy. If each character has 5.9542 bits of entropy as discussed above, it gives about 12 for the required session id length, but you can just round it up to 16 to make sure. :)
As a rule of thumb, it is sometimes assumed that bits of entropy in a session id is half the length (in bits) of that session id. It is mostly a reasonable approximation without any calculation. :) Even more so, because sessuion ids are sometimes actual random numbers base64 or otherwise encoded. Different encodings usually give different results though.
Also make sure to use a cryptographic random number generator, otherwise entropy is much less. Note that mt_rand() is not cryptographically random, so the code in your question is vulnerable!
2) TL;DR Yes. (I suppose you mean using local storage for storing the session id.)
The best possible place to store a session id is a httpOnly, Secure cookie without an expiration (non-persistent), because Javascript cannot access it there (for example cross-site scripting doesn't affect a victim user's session id at least), and being non-persistent, it will be removed when the user closes the browser and will not be persisted to disk (well, mostly... but that's a long story).
If you use localStorage, any XSS will directly affect the session id, which is very valuable for an attacker. Also sessions will survive closing the browser, which is slightly unexpected - user sessuions might easily be hijacked on shared computers.
Note though that this depends on the use-case and the risk you want to take. While it would definitaly not be ok for a financial application where you can access and manage very sensitive data, it can be ok for less risky applications. You can also let the user decide ("remember me", in which case you put it into localStorage), but most users are not aware of the associated risk, so they can't make an informed decision.
Also note that sessionStorage is a little better, because the session id will be removed from the browser when it is closed, but it is still available to Javascript (XSS).
I am using express-session and I need to create a session secret.
I have read that the secret is used for hashing.
How long and what characters should this secret have?
I was thinking about a random alphanumerical string like this:
IqFic484907I0T552hiMQ1UCJimRGL55
Can anyone give me advice on this?
The secret is used to sign the session id cookie, to prevent the cookie to be tampered with.
In the end, the module responsible for creating the signature is cookie-signature, which uses crypto.createHmac() using the secret as the key and the SHA256 algorithm for hashing.
I don't think there's a real upper limit to the length of the secret (other than, possibly, the maximum length of a string in Node, which I don't even think exists). I think that if it's longer than 256 bits (= 32 bytes), it will get hashed down to 32 bytes first.
There's also no limitation to which characters you put in a secret. A string of random alnum characters, say 24 to 32 bytes long, should do just fine.
I'd take the following steps to send/received data between client and server. But I'm not sure if all the steps are secure enough and impossible to intercept. Please can you let me know how to patch up the security holes, if any?
Please note that:
This is all for Symmetric cryptography not the Public/Private Key method. So 'salt' and 'entropy' are the secrets between client and server.
For some reasons, I cannot/don't use SSL or TLS protocols. So there is no Certificate Authority etc.
Only standard cryptography functions and methods must be used (no inventions).
The data are not sent over a secure (HTTPS) connection.
I cannot use sessions here because they are two different applications.
First time - Sign up (user enters new username and new password)Client sideCreate a CSPRNG salt for the userSave salt on user's machineCreate an entropy in memory (base64 of a temporal value e.g. [hour of the day]+[day of the year])Post Password(base64 of the plaintext), and salt, and entropy to the serverServer sideFor the received message from the clietCheck if entropy matches (e.g. with base64 of [hour of the day]+[day of the year]) .Compute hash (SHA1) of salt and Password(base64 of the plaintext) - because hashing must always be done on the server.Save salt and the computed hash to the database
Next time - Log in (user enters his username and his password)Client sideRead salt from user's machineCompute hash (SHA1) of salt and entered password(base64 of the plaintext)Post password(base64 of the plaintext), and salt, and the computed hash to the serverServer sideRetrieve salt and 'stored hash' from the databaseFrom the received message from the clientSplit the message to 3 parts: password(base64 of the paintext); salt; 'received hash'Compare 'received hash' with 'stored hash'; if they match, the user is genuine not a hacker
Sending TemperProof queryString from user to serverClient sideRead salt from user's machineCreate an entropy in memory (base64 of a temporal value e.g. [hour of the day]+[day of the year])Compute hash (SHA1) of the queryString(base64 of the plaintext), and salt, and entropyPost querystring(base64 of the plaintext), and salt, and entropy, and the computed hash to the serverServer sideRetrieve salt from the databaseFor the received message from the clietSplit the message to 4 parts: queryString(base64 of the paintext); salt; entropy; 'received hash'Check if entropy matches (e.g. with base64 of [hour of the day]+[day of the year]) .Compute hash (SHA1) of queryString(base64 of the plaintext) and salt and entropy.Compare the computed hash with the 4th part of the splitted message ('received hash'); if they match, the queryString is genuine
Sending answer back to the user from serverServer sideCompute the answer using database queriesRetrieve salt from the databaseCreate an entropy in memory (base64 of a temporal value e.g. [hour of the day]+[day of the year])Compute hash (SHA1) of the answer(base64 of the plaintext), and salt, and entropyPost answer(base64 of the plaintext), and salt, and entropy, and the computed hash to the clientClient sideRead salt from user's machineCreate an entropy in memory (base64 of a temporal value e.g. [hour of the day]+[day of the year])Split the received message to 4 parts: answer(base64 of the paintext); salt; entropy; 'received hash'Check if entropy matches (e.g. with base64 of [hour of the day]+[day of the year]) .Compute hash (SHA1) of answer(base64 of the plaintext) and salt and entropy.Compare the computed hash with the 4th part of the splitted message ('received hash'); if they match, the answer is genuine
The followings areas are the weaknesses, I think. Can you please advise how these can be fixed and also point out the other possible holes?
A) First time - Sign up: The hacker can post rubbish to fill up the database, if he finds entropy in step 1.3
B) Next time - Log in: I cannot think of a way to add an entropy to the hashed password in step 1.2, because then I cannot compare with the one on the server database in step 2.2.2
Thanks
if you care about security at all, STOP now ...
what you need to do:
1) forget about designing your own crypto protocol ... relying on well known crypto ALSO means that you DO NOT design that kind of thing
2) think in layers ... you have the need to keep things secret while transporting them from A to B ... that means you have a transport layer ... if you want that secured, there is a name for that...
Transport Layer Security -> https://en.wikipedia.org/wiki/Transport_Layer_Security
3) when you make assumptions here like "i have 2 applications so i can not have sessions", please provide WHY you think it is that way... when you think of things like single-sign-on, you can have a lot of applications sharing one authentication method and even session data across a bunch of different plattforms ... maybe it's just you don't know that you actually can have sessions...
4) read up on the terms you use ... you misunderstood entropy ... there is no way of "checking if entropy matches" ... entropy in crypto related cases means randomness in terms of unpredictable input to a function ... if you have something like a date and the time, and even if you hash that, it might look random ... but it is very predictable by someone with a clock... if the communication can be related to the creation time of the value, then your value does not contain large amounts of entropy if it is based on the clock... again, do not design you own stuff here, and go for entropy sources that provide reliable, cryptographically secure randomness (CSPRNG ... not just PRNG)
5) ... you misunderstood/misused salt...
that is nothing to be generated on the client machine
that is nothing that needs to be kept on the client machine
7) password hashing
again ... DO NOT come up with you own stuff here ...
create a sufficently long random salt (at least hash length) for every password. use a slow hash function like PBKDF2 with a high iteration count parameter. the reason is that it becomes slow to test for passwords... your server has to calculate this once for every login attempt ... an attacker has to calculate this for every password testing attempt ... you can afford the test to take like 200ms ... for an attacker that means a lot more hardware will be needed to crack your password storage...
upd:
you want it, you get it ...
proof of concept attack on your schema by man-in-the-middle:
client: alice
server: bob
attacker/eavesdropper: eve
alice uses your sign up service and creates an account
1.1 alice creates a CSPRNG salt and stores that in a secure manner
1.2 alice gathers an arbitrary amount of entropy and encodes it with base64
1.3 alice sends Password(base64 of the plaintext), and salt, and entropy to bob
------------intercepted-------------
eve intercepts the communication between alice and bob and gains knowledge of...
2.1 ...the base 64 encoded password -> base64 decode -> the plaintext password
2.2 ...the salt
2.3 ...the entropy value
2.4 alice forwards the intercepted communication without changes to bob
--- protocol broken ---
now, bob is by no means able to distinguish between alice and eve in all further communication
upd2:
a look on your transfered (cleartext) messages:
Login:
Post password(base64 of the plaintext), and salt, and the computed hash to the server
Sending queryString from user to server:
Post querystring(base64 of the plaintext), and salt, and entropy, and the computed hash to the server
answer:
Post answer(base64 of the plaintext), and salt, and entropy, and the computed hash to the client
now for any of those messages, let's look at what information someone with malicious intent would learn from those:
all the information that is cleartext, which means, all of it ...
for the login, we gain the clear text password, means from now on an attacker can identify as a valid user
for the querystring and answer thing you want to provide a way to see if the request/answer is not tempered with.
so if an attacker now intercepts your communication how can he change the querystring without being noticed?
the attacker splits your message, and changes whatever he/she wants
then he/she computates the forged_hash as sha1(salt_from_the_original_message,tampered_querystring) and sends base64(tampered_querystring),salt_from_the_original_message,entropy_from_original_message,forged_hash to the server ...
for the answer it's the same deal:
the attacker intercepts the original answer, changes whatever in the answer and recomputes the hash based on known information (the changes, and the original salt)
The solution is to use HTTPS withTLS 1.2 and pin the certificate, there are certificate solutions that are free.
Using a hash (SHA1 in this case) to protect a password has not been good secure practice for some time. For my reference see DRAFT NIST Special Publication 800-63B Digital Authentication Guideline.
Passwords must be protected with an iterated HMAC, not a single hash. For more information on passwords see Toward Better Password Requirements by Jim Fenton, see slide 23 in particular. This seems to be at odds with the Pluralsight training, best practices have changes over time.
Currently I am using a particular scheme for securing passwords, and I think I have some points for improvement. The implementation is in Java, so I prefer to use SHA-2 512 as encryption form.
Currently I have a client-server model, so these things can happen:
Client wants to login, he sends his password with one time normal SHA-2 512 encryption over the network.
The server has the passwords stored in the database as for example SHA-2_512(SHA-2_512(password) + salt), with the inner SHA-2_512(password) being the 'encrypted' password it receives over the network.
Password checks are done server side and there is no way anything can leak out from the server, the only possible vulnerability would be if someone could read out the RAM I think.
I have these questions:
An attacker usually creates collision attacks when wanting to hack a password. However how are collision attacks sufficient? If the password needs to be used for other applications like Outlook.com, Facebook or whatever (which likely use another salt as they have nothing to do with my applications), how is a collision attack enough then? Don't you need the real password?
Does SHA-2 512 already use iteration? And even if so, should I change my encryption methods to automatically use a number of iterations plus how many iterations is preferred? I have also read about using a random number of iterations (in a range), how do I store the random factor determenistically?
Should I store system secrets for every iteration in the server code? See http://blog.mozilla.org/webappsec/2011/05/10/sha-512-w-per-user-salts-is-not-enough/ . I could store an array which would hold a static secret for every iteration, with the nth secret being for the nth iteration. Nobody can know the secrets, they are computed once (I guess as encrypting some random string), and then basically stored in the Server's RAM.
Currently I send the typed password from the client to the server as just SHA-2_512(password), should this process be improved, and if so, how? I cannot use salts, because the client does not have the salt available.
Regards.
TLDR: You need to send the password using an encrypted channel, such as TLS. Consider using bcrypt for password hashing.
SHA-2 512 is not an encryption algortihm, it is a message digest algorithm. An encryption algorithm requires a key and a message to encrypt. It produces ciphertext. The important thing is that an encryption algorithm has a decryption algorithm.
ciphertext = E(key, plaintext);
plaintext = D(key, ciphertext);
A message digest takes a piece of plaintext and produces a message digest. There is no corresponding reverse mechanism to take a message digest and retrieve the original message. There is also no secret key.
digest = hash(plaintext);
If an attacker is able to access a database with hashes, the attacker can retrieve the original password by brute forcing, trying lots of guesses with the hash algorithm.
digest1 = hash(guess1);
digest2 = hash(guess2); //repeat with lots of guesses
Firstly, sending a hash over a network is not secure. It needs to be sent through some secure communications mechanism such as SSL. If an attacker can intercept the hash over the communications they may be able to work out the orignal password.
A hash collision is not the same as brute forcing the password. A hash collision is caused when two different messages produce the same message digest.
digest1 = hash(plaintext1);
digest2 = hash(plaintext2);
if ( ( plaintext1 != plaintext2 ) && ( digest1 == digest2 ) )
// hash collision
SHA-512 does not have iterations designed to prevent brute-forcing. The SHA set of algorithms are designed to be efficient. The reason for adding iterations when hashing passwords is to increase the time it takes to brute force a password. The idea being the cost to perform a legitimate login attempt and perform 100 iterations is tiny compared to an attacker who has millions of passwords, each of which requires 100 iterations. Adding more iterations helps reduce the impact of improved processor speeds (which would help an attacker try more iterations quicker).
You should make the number of iterations a configurable limit that is stored against each user. So you store the password hash, salt and iteration count for each user. This means that in the future you can increase the number of iterations to take into account increased hardware power.
Sending the SHA-2 512 in plaintext is not secure. You should send it within an encrypted channel, such as SSL.
Having said all that, SHA-2 is not designed to be a password hashing algorithm. It is designed for message validation and is to be efficient. Consider using a purpose built password hashing algorithm. One example is bcrypt. It is designed to be computationally difficult and has salt and iterations built in.