WebauthN and Yubikey to generate keys for symmetric encryption in a browser. Is this hack secure? - encryption

I understand that WebauthN is designed to perform authentication, but I'd like to use my Yubikey to create symmetric encryption keys to encrypt content on my web browser without relying on a backend server.
Here's my approach:
During the assertion challange ( navigator.credentials.get({ publicKey }) ) the Yubikey signs a challenge string that the client sends to the authenticator.
I extract the signed challenge, hash it with SHA256 and use it as my new symmetric encryption key for AES256 encryption.
As long as the same challenge string is sent to the Yubikey, the encryption key will always be the same.
In order to be able to decrypt the content on the web browser. I would have to be in possession of the Yubikey and the challenge string for 2 factor authentication.
Is there anything wrong with this approach?

Why don't you use the Web Crypto API?
This API is designed for cypher operations on client side and is suitable for your use case (client side encryption). It is supported by all recent browsers.
Note that the main concern you may have is that this API does not supports hardware devices (smartcards, security tokens...).
However, your Yubikey is certainly capable of generating a secured static password you can use as a master key you will derive to encrypt/decrypt your data.

Perhaps you could leverage the hmac-secret extension (https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#sctn-hmac-secret-extension)?

No. This is a bad idea for these reasons:
RS256/ES256 are not deterministic signatures. So you will get a new, random signature every time.
Even if you could, there are things like XSS, and this would be broken swiftly.
Use crypto API as mentioned above.
HMAC-Secret is reserved for Platforms at the moment. You can not access it via WebAuthn API.
In the future, there is a large blobs functionality, and largeBlobsKey... But this is a very long future...

You could (mis)use the user.id parameter of the public key request payload, i.e., the user object in the example here: Web Authentication API (example) like this:
Use Web Crypto API to generate a symmetric key
then use that key as user.id in navigator.credentials.create({ user: {id: YOUR_KEY } }) to store the key in the authenticator
store the returned key id/rawId somewhere in your application
To retrieve the key you can use navigator.credentials.get({ publicKey }) by supplying the rawId

Related

Secure Encryption Passing Sensitive Info to Server / API in Firebase Cloud Functions?

So I have an app that uses Firebase/Cloud functions. On the client, I have users enter their:
Bank Account Number
Bank Routing Number
And (on my client) I pass those variable as parameters into a cloud function… like this:
final _cloudFunctions = FirebaseFunctions.instanceFor(region: 'us-central1');
final _createCustomerAndAddBankingInfo = _cloudFunctions.httpsCallable(
'createCustomerAndAddBankingInfo',
);
await _createCustomerAndAddBankingInfo.call(<String, dynamic>{
'firstName': firstName,
'lastName': lastName,
'routingNumber': routingNumber, // these are not encrypted, these are literally the numbers e.g. `01234567`
'accountNumber': accountNumber,
'accountNickname': accountNickname,
'accountType': accountType.toLowerCase()
})
Then (on the server) I use the cloud function to pass that data to the Dwolla API (which already uses TLS) and I DO NOT STORE their routing / acct number anywhere in my database.
Since this is sensitive information, do I need to encrypt it on the client and then decrypt it inside that cloud function?
Or does cloud functions provide encryption when you pass parameters through as variables?
I scoured the docs online and I can’t find a concrete answer to this question.
I found this:
https://cloud.google.com/firestore/docs/server-side-encryption
...but I'm unsure if this applies just to reads/writes or if this also applies to functions.
I really appreciate the help! Thank you!
https://security.stackexchange.com/questions/110415/is-it-ok-to-send-plain-text-password-over-https applies to your question:
It is standard practice to send "plaintext" passwords over HTTPS. The passwords are ultimately not plaintext, since the client-server communication is encrypted as per TLS.
Encrypting the password before sending it in HTTPS doesn't accomplish much: if the attacker got their hands on the encrypted password they could simply use it as if it were the actual password, the server wouldn't know the difference. The only advantage it would provide is protecting users that use the same password for multiple sites, but it wouldn't make your site any safer.
As long as your are communicating with Cloud Functions over HTTPS, encrypting Bank Account Number/Bank Routing Number is not achieving much.
Fortunately, you are already using HTTPS to communicate with your Cloud Function instance, as noted in https://firebase.google.com/docs/functions/callable-reference.
Short answer is yes, you will need to encrypt it on the client before sending it.
The way you would do this however can vary.
What I suggest is using a Cloud function to generate a one-use crypto hash and store it in a private non-readable database based on the user uid and a timestamp and then send it to the client.
The client then uses the secret key to encode the data and send it to a new cloud function that decodes it using the key from the database rather than having the key sent with the encoded payload
It is suggested that you store the encoded data with the timestamp mentioned earlier to validate/decode in the future in a two-step process so that if your database is ever compromised, the data is still encoded.
var encryptedAES = CryptoJS.AES.encrypt("Message", "My Secret Passphrase");
var decryptedBytes = CryptoJS.AES.decrypt(encryptedAES, "My Secret Passphrase");
var plaintext = decryptedBytes.toString(CryptoJS.enc.Utf8);
https://code.google.com/archive/p/crypto-js/downloads
Please note: that this is a simplified solution and you should look at additional steps to further enhance the encryption methods on top of this, including 3-way handshakes, randomized client/user based ID's, and other randomizing agents built within your app. While HTTPS is fairly secure, there is no guarantee that it is 100% secure.

Webauthn for encryption

We have a project with a PWA where we want to implement client sided encryption. We wanted to use Webauthn as a second-factor in combination with passwords. In the background we use a randomly generated key to encrypt/decrypt the database, which is stored symmetrically encrypted with the password on the server. However I am struggling to find a good way to add encryption to this key with webauthn. My tries so far:
Using raw JS samples from https://webauthn.guide , however I cannot find a part which is always the same and could be used for symmetric encryption/decryption, even the public key changes when logging in with the same USB token multiple times (???)
Using fido2-lib from npm: I couldn't get the sample to work, since the sample is not well documented and pretty long
Using server-sided authentication like spring webauthn, however I do not want the server to know anything about the client.
Any suggestions how I could implement an encryption with webauthn?
The protocol as it stands does not provide generic public key crypto services as far as I am aware. The best you can do is prove that a user is in possession of the private key related to the public key you hold.
You can learn from the following github repo ,it has many Webauthn out of the box examples (see the tech it supports inside)
Here are some samples I found at github https://github.com/OwnID/samples
In addition,I read about FIDO ,Webauthn and passkeys at passkeys.com
Everything about this cool tech is there
Years after this question, the hmac-secret extension has arrived.
This extension binds a secret to a Webauthn credential. This secret can be used to decrypt or encrypt data on client side.
Another approach could be the use of the largeBlob to store a secret generated during the creation ceremony.
Note that the availability of those extensions depends on the authenticator that is used and may fail.

How to encrypt and decrypt on the client that may come from a different device and the server doesn't store any encrypted key?

I am making a password management tool like Lasspass for myself and family to use, but I have encountered some problems in security. After reading LastPass-Technical-Whitepaper, I am very curious about how they do it only on the client side.
According to the "Local-Only Encryption Model" they describe, the server only stores the encrypted infomation.
Does this mean that they don't store any key or IV or salt for AES decryption on their server side?
As far as I know, this may be fine if I encrypt and decrypt on the same device, but I use Lasspass to add a password on the computer, but I can see the password on the mobile phone.
How do they decrypt it on the mobile phone?
Should the mobile phone have no key and IV and salt for computer-side encryption, isn't it?
I am currently using ASP.NET Core 3.0+React+identityserver
How should the truly secure "Local-Only Encryption Model" be implemented?
Could someone give me some direction?
Thanks for any help.
first: i don't know about the lastpass implementation, or the paper
but let's have a look at what we can do...
let's say we have a server that stores binary data for us, and further, it does so on a per user/account basis...
what do we need to be able to authorize account functions like "replace data" or "request stored data"?
we need a task that has to be solved by the client that proofs the client-identity, in other words an operation that can be solved if-and-only-if the client holds a specific secret... this screams for asymetric crypto and/or digital signatures ... keypairs ...
how could we do this if we have no shared storage to safely store a key and share it between 2 different devices?
one possible solution is quite simple and starts with... PBKDF2 ... Password Derived Key Derivation Function
PBKDF2 takes a password, a salt, a parameter for the number of rounds, and gives you... bits... pseudo random bits
Take your username and hash it ... => a perfect salt...
Take an arbitrary number like 15000 => your number of rounds (larger numbers slow down the process of calculating -> slowing down bruteforce attacks while you only need to calculate it once every login)
Take your password => surprise... a password
run PBKDF2
seed your favourite CSPRNG (Cryptographically Secure Pseudo Random Number Generator) with a few bytes from the resulting bitstream
use your favourite CSPRNG to generate a new (RSA,DSA,ECDSA,whatever) keypair ...
use the key
this EXACT key pair will be generated EVERY time you run these steps, on any device
of course this breaks down the security of said keypair to the strength of the secret parameters to PBKDF2, which will be... your password
but now you have asymetric crypto from a password...
the server stores the public key ... and just asks the client to decrypt a number (a nonce) together with the request to replace/retrieve the data... being able to decrypt the nonce proofs that the secret key is known to the client
you can also derive an AES key from the PBKDF2 Stream to encrypt your Password database before you send it to the server ... or to decrypt it once it has been recieved

Is it secure to pass static value for CryptoJS AES encryption key?

I want to encrypt all my form data with crypto js AES encryption. Is it good to keep the encrypt key value in client side like below.Kindly suggest.
var key = CryptoJS.enc.Utf8.parse("234234234DFDFDF343434DFDFDF")
, iv = CryptoJS.enc.Utf8.parse("234234324234324")
, data = CryptoJS.enc.Utf8.parse(str)
, encryptedData = CryptoJS.AES.encrypt(data, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
No, this is generally not secure.
First of all, you wrongly state your goal. AES encryption is not a goal, it is a means to an end. What you want to do is to keep your form data confidential. The form data is called "the message" in crypto terminology.
If you need to send your message to a server, and then let the server have the decrypted form, then you need transport security. It's best to use TLS for that. Commonly JavaScript doesn't have any means to create a trust relationship with the server (your browser uses it's certificate store with trusted certificates for that). So in that case you cannot rely on JavaScript security.
Sometimes you want to keep your front end from decrypting the messages. In that case you could send a public key such as an RSA public key over the secure channel. Then you could encrypt the form data using RSA and AES (hybrid cryptography). Your backend would then be able to decrypt the messages. This scheme still fails if your front end sends the wrong key though, as the message would be encrypted with a public key from another party. So this assumes that the front end software cannot be easily hacked (or replaced altogether by a different server, subverting the traffic).
Even if you could use a trusted AES key then using CBC for transport mode security will definitely enable plaintext or padding oracle attacks. You're lacking way too much experience with crypto to pull this off. Use TLS, that's hard enough (to secure, using it is relatively easy).

Exchanging Symmetric Keys

I have a WinForms client that is sending encrypted data to a web service. The WinForms client creates a Symmetric RijndaelManaged sessionKey and also has a "hard-coded RSA asymmetric public key".
I am using the EncryptedXml class which makes it really easy to package up my data.
The Web Service has both the private and public keys "hardcoded" and can successfully decrypt the SessionKey and then use it to decrypt the actual data I am sending.
This is pretty much handled automatically by the EncryptedData class.
The problem I am having is that on the Web Service end when I want to reply, I can't seem to figure out how to get the SessionKey that was sent over.
Before I do any decrypting on the Web Service side, I can see the encrypted session key, but after I decrypt the XML, it's gone (and therefore I don't have any session keys for my reply).
Any ideas how I can get this unencrypted key?
The reason that you cannot see the session key is that it is automatically decrypted and used. Normally it is considered part of the XML. If you want to get at it, just use
encryptedxml.decryptencryptedkey
And you should be alright. Note that for all the less important security warnings, the code represented here is vulnerable to both man in the middle attacks and to a lesser extend to padding oracle attacks. It should help against most eavesdropping attempts though.
Finally, reusing a session key is considered pretty bad security practice too. If you reuse it, at least consider using an IV, e.g. a counter for each followup message.

Resources