Suppose an attacker got a list of the TOTP codes that I had generated with an authenticator app. The attacker also knows the timestamps at which the authenticator app had produced the codes.
code timestamp
182038 1671981349726
774903 1671981333299
238982 1671981328102
Using that information, would it be feasible for the attacker derive or somehow deduce the secret? Or could they use this data to reduce the possible range of values that the secret could be to a small enough list, such that they could use brute force to go the rest of the way?
Short answer: Feasible? No.
The security of the TOTP algorithm against this attack is based on the difficulty of obtaining an exact input to the SHA-1 hash function when given some bits from its output. This was one of the design considerations of HOTP and TOTP, and it is considered that the best attack on it is still brute force of the secret key shared between the parties at initialization time. If that key is secure, TOTP should be secure against publishing timestamps and TOTP values.
Related
I'm trying to authenticate a user after registration. What's the correct or standard way to go about it?
Using this method as the way to implement it, in step 3, how can I generate the random hash to send to the users email? I see two different options:
crypto
JWT token
I'm currently using JWT for login, so would it make sense to use the same token for user verification? Why or why not, and if not, what's the correct way?
The answer to your question of whether you should use a crypto hash or a token is neither.
The hash you are generating to use as a verification method does not need to be cryptographically secure, it only needs to be a unique verification hash that is not easy to guess.
In the past I have used a v4 UUID with the UUID lib and it works just fine. You could also base64 some known piece of information about the user, like their id or email concatenated with something random, like the time in mircoseconds and a random hex string with substantial length, but honestly the time it takes to build out something like that is wasted when UUID v4 works just fine.
Your hash also doesn't need to be unique (different for each user, yes, but avoid all potential collisions? No) - hitting an endpoint with only the hash is not a great idea. The endpoint should also take an identifier for your user combined with the verification hash. This way, you don't need to worry about the hash being unique in your datastore. Find user by ID, check that verification hashes match, verify. I would only suggest that you obfuscate the user's know information in a way that you can decode on your end (ex: base64 encode their user ID + email + some const string you use).
[EDIT]
Verifying or validating a user is really just asking them to prove that the email address (or phone number) they entered does in fact exist and that it belongs to the user. This is an attempt to make sure the user didn't enter the information incorrectly or that the registration is spam. For this we don't need cryptographic authentication, a simple shared secret is more than enough.
When you store your user's registration data, you generate the shared secret you will use to verify the account. This can be anything that is (relatively) unique and contains enough length and entropy that it is not easy to be guessed. We aren't encoding or encrypting information that will be unpacked later, we are doing a literal string comparison to make sure the secret we provided to the user was echoed back to us intact. This is why a simple one-way hash is OK to use. I suggested a UUID v4 because the components of this hash are generated from random information (other versions of UUID make use of the machine's MAC or the time or other known pieces of information). You can use any method you like as long as it can't be easily decoded or guessed.
After you generate the verification hash you send it to the user (in a nicely formatted URL that they only need to click) in order for them to complete their account registration. URL guidelines are totally up to you, but here are some suggestions:
BAD
/verify/<verification hash>
or
/verify?hash=<verification hash>
With only the verification hash in the URL, you are relying on this value to be globally unique in your datastore. If you can reliably generate unique values that never contain collisions, then it would be OK, but why would you want to worry about that? Don't rely on the verification hash by itself.
GOOD
/users/<id>/verify/<verification hash>
or
/users/<id>?action=verify&hash=<verification hash>
Out of these two examples you can see that the point is to provide two pieces of data, 1. is a way to identify the user, and 2. the verification hash you are checking.
In this process you start by finding the user in your datastore by ID, and then literally compare the secret you generated and stored against the value given in the URL. If the user is found and the verification hashes match, set their account to Active and you're good to go. If the user is found but the hashes don't match... either you provided a malformed URL or someone is trying to brute force your verification. What you do here is up to you, but to be safe you might regenerate the hash and send out a new email and try the process again. This leads very quickly into a black hole about how to prevent spam and misuse of your system, which is a different conversation.
The above URL schemas really only work if your user IDs are safe for public display. As a general rule you should never use your datastore IDs in a URL, especially if they are sequential INTs. There are many options for IDs that you would use in a URL like UUID v1 or HashIDs or any implementation of a short ID.
ALSO
A good way to see how this is done in the wild is to look at the emails you have received from other systems asking you to verify your own email address. Many may use the format:
/account/verify/<very long hash>
In this instance, the "very long hash" is usually generated by a library that either creates a datastore table just for the purpose of account verification (and the hash is stored in that table) or is decoded to reveal a user identifier as well as some sort of verification hash. This string is encoded in a way that is not easily reversible so it can not be guessed or brute forced. This is typically done by encoding the components with some sort of unique salt value for each string.
NOTE - while this method may be the most "secure", I only mention this because it is based on the typical methods used by third-party libs which do not make assumptions about your user data model. You can implement this style if you want, but it would be more work. My answer is focused your intent to do basic verification based on data in your user model.
BONUS
Many verification systems are also time constricted so that the verification URL expires after some period of time. This is easily able to be set up by also storing a future timestamp with your user data that is checked when the verification endpoint is hit and the user is found. What to do when an expired link is clicked is up to you, but the main benefit is to help you more easily clean up dead registrations that you know cannot be verified.
What security measures should I put in place to ensure that, were my database to be compromised, long-life access tokens could not be stolen?
A long-life access token is as good as a username and password for a particular service, but from talking to others it seems most (myself included) store access tokens in plain text. This seems to be to be just as bad as storing a password in plain text. Obviously one cannot salt & hash the token.
Ideally I'd want to encrypt them, but I'm unsure of the best way to do this, especially on an open source project.
I imagine the answer to this question is similar to one on storing payment info and PCI compliance, but I'd also ask why there isn't more discussion of this? Perhaps I'm missing something.
Do you just want to verify a token provided by others? If so, treat it as you would a password. Use a byte derivation algorithm like Password Based Key Derivation Function 2 (PBKDF2) (also described in RFC 2898) with 10,000 iterations and store the first 20 bytes or so. When the token is received. It is not practically reversible.
Do you want to present the token to others for authentication? If so, this is a challenge because, if your application can decrypt or otherwise get access to the token, so can an attacker. Think Shannon's Maxim, the attacker knows the system, especially for an open source project.
In this case, the best approach is to encrypt the tokens with a strong algorithm (e.g. AES256), generate keys using a strong cryptographic standard random number generator and store the key(s) securely in a different location to the data, such as in a permission protected file outside the database in the example above. The latter means that SQL injection attacks will not reveal the keys.
In one of my applications, I am to store user credentials and tokens. As the credentials are used directly on third-party services, I cannot hash them and thus need to store them as-is.
As I am not an expert on encryption, I googled and found that AES 256-bit key size-is a good idea to encrypt such data.
I would like to know the opinion of the SO community on the same, before I make a final decision.
Thanks!
Edit: Thanks to all for discussion, I am moving ahead using AES256 as the encryption mechanism for now, which seems like a good choice.
if you ask user for credential every time, then why do you need to store them in db? just keep it in memory and pass to external system. you can even ask user once and keep their password in memory for the whole session. if, for some reason you have to store them in db, them of course encrypt it. as far as i know, current standard is AES256. but still somewhere you have to keep unencrypted key.
to sum up: if you want to authenticate users and then use their password only for the time of session then you don't have to store it in database. keep salted hash for authentication purpose and keep user provided password in session for external systems
btw. is your swap encrypted?
I realize that the OAuth spec doesn't specify anything about the origin of the ConsumerKey, ConsumerSecret, AccessToken, RequestToken, TokenSecret, or Verifier code, but I'm curious if there are any best practices for creating significantly secure tokens (especially Token/Secret combinations).
As I see it, there are a few approaches to creating the tokens:
Just use random bytes, store in DB associated with consumer/user
Hash some user/consumer-specific data, store in DB associated with consumer/user
Encrypt user/consumer-specific data
Advantages to (1) are the database is the only source of the information which seems the most secure. It would be harder to run an attack against than (2) or (3).
Hashing real data (2) would allow re-generating the token from presumably already known data. Might not really provide any advantages to (1) since would need to store/lookup anyway. More CPU intensive than (1).
Encrypting real data (3) would allow decrypting to know information. This would require less storage & potentially fewer lookups than (1) & (2), but potentially less secure as well.
Are there any other approaches/advantages/disadvantages that should be considered?
EDIT: another consideration is that there MUST be some sort of random value in the Tokens as there must exist the ability to expire and reissue new tokens so it must not be only comprised of real data.
Follow On Questions:
Is there a minimum Token length to make significantly cryptographically secure? As I understand it, longer Token Secrets would create more secure signatures. Is this understanding correct?
Are there advantages to using a particular encoding over another from a hashing perspective? For instance, I see a lot of APIs using hex encodings (e.g. GUID strings). In the OAuth signing algorithm, the Token is used as a string. With a hex string, the available character set would be much smaller (more predictable) than say with a Base64 encoding. It seems to me that for two strings of equal length, the one with the larger character set would have a better/wider hash distribution. This seems to me that it would improve the security. Is this assumption correct?
The OAuth spec raises this very issue in 11.10 Entropy of Secrets.
OAuth says nothing about token except that it has a secret associated with it. So all the schemes you mentioned would work. Our token evolved as the sites get bigger. Here are the versions we used before,
Our first token is an encrypted BLOB with username, token secret and expiration etc. The problem is that we can't revoke tokens without any record on host.
So we changed it to store everything in database and the token is simply an random number used as the key to the database. It has a username index so it's easy to list all the tokens for a user and revoke it.
We get quite few hacking activities. With random number, we have to go to database to know if the token is valid. So we went back to encrypted BLOB again. This time, the token only contains encrypted value of the key and expiration. So we can detect invalid or expired tokens without going to the database.
Some implementation details that may help you,
Add a version in the token so you can change token format without breaking existing ones. All our token has first byte as version.
Use URL-safe version of Base64 to encode the BLOB so you don't have to deal with the URL-encoding issues, which makes debugging more difficult with OAuth signature, because you may see triple encoded basestring.
I was given advice that I am suspicious about so I'm looking for support here to go back and challenge the advice.
I was advised to use Diffie-Hellman to get both sides to agree on a secret key, use the secret key to generate an AES key, and then use AES to encrypt/decrypt passwords that are being transmitted. Pretty much like the sample code here
When using this scheme, the length of the encrypted password is the same as the length of the unencrypted password. Should I be worried about this?
Before, I was using RSA, encrypting the passwords with the receiver's public key. This was resulting in an encrypted length of 256 no matter what the password length. Isn't that better?
You can just pad to whatever length with any data. It doesn't have to be random. As long as it's all encrypted. I think though that is the least of your worries.
Note if you use Diffie-Hellman you still need to authenticate the parameters sent, which you probably need to do with RSA.
The alternatives are:
Use RSA to exchange an encrypted secret key that you then use to encrypt your data.
Use Diffie-Hellman to exchange a secret key and then use RSA to sign values sent to authenticate the transaction.
If you do all this, then you have to also worry about whether exchanges have been replayed to make you reuse keys etc.
To be honest if you need to ask this question then you probably are not qualified to write a crypto protocol. They are extremely hard to get right and not for the faint hearted.
Suggest you use SSL/TLS for your exchange if you need to stream a lot of data. PGP/PKCS#7 if you just need to send a single message.
First off: Don't invent your own authentication protocol. Period. If you do, you WILL get it wrong even if you're using strong encryption. There are a number of existing well documented authentication protocols that have been vetted by cryptographers and thus are thought to be secure. Don't be tempted to "simplify" them, they've already been simplified.
Second: IMHO you should never send passwords on the wire for authentication (I'm not aware of any authentication protocol which does, including the hideously insecure NTLMv1 protocol)[1].
If you're dead set on going down the "roll my own authentication scheme" path, here's how I'd make the scheme you described above more secure (Caveat: I'm not a cryptographer - I believe that there are serious weaknesses in what I'm describing here):
Instead of sending the password directly, send a one-way-function (also known as a OWF, often implemented as a cryptographic hash like SHA256 or stronger) of the password.
In other words, have the server send the client a salt value, add the salt to the password, compute the OWF of the password+salt value and send the OWF result to the server. On the server, add the salt to the password and also perform the OWF calculation. If the results are the same, the password is valid, if they're not it's invalid.
And finally have whatever you do reviewed by a real cryptographer. They will find problems in your implementation and you're going to have to fix them. They're likely to suggest that you abandon your effort in favor of an existing published protocol.
[1] AFAIK, the only time you should send the password on the wire is when you're changing the password and even then, you should pad the length to a multiple of the block size (include the length in the cybertext so that when you decrypt it you can distinguish between the password and the padding).
If you can help it, don't send passwords over the wire at all. Instead, use a scheme like SRP, which authenticates both parties with one password.