electron / electron

:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS
https://electronjs.org
MIT License
114.41k stars 15.44k forks source link

[Bug]: safeStorage.decryptString - Error while decrypting the ciphertext #32598

Closed pwasem closed 2 years ago

pwasem commented 2 years ago

Preflight Checklist

Electron Version

16.0.7

What operating system are you using?

macOS

Operating System Version

macOS Monterey 12.1

What arch are you using?

x64

Last Known Working Electron version

No response

Expected Behavior

safeStorage.decryptString(buffer) should decrypt a previously encrypted string (using safeStorage.encryptString(plainText))

Given the following module for storing settings for an application.

Settings are stored to a file and retrieved on next app start.

Sensitive information, e.g. personalAccessToken should only be stored encrypted.

const { join } = require('path')
const { existsSync, promises: { readFile, writeFile } } = require('fs')
const { app, safeStorage } = require('electron')
const { Buffer } = require('buffer')

const userData = app.getPath('userData')
const settingsPath = join(userData, 'settings.json')

const defaultSettings = {
  baseUrl: '',
  personalAccessToken: '',
  fetchInterval: 5
}

const encrypt = plainText => {
  const buffer = safeStorage.encryptString(plainText)
  const encrypted = buffer.toString('base64')
  return encrypted
}

const decrypt = encrypted => {
  const buffer = Buffer.from(encrypted, 'base64')
  const plainText = safeStorage.decryptString(buffer)
  return plainText
}

module.exports = {
  async save ({ baseUrl, personalAccessToken, fetchInterval }) {
    const json = JSON.stringify({
      baseUrl,
      personalAccessToken: encrypt(personalAccessToken),
      fetchInterval
    })
    await writeFile(settingsPath, json, 'utf8')
  },

  async load () {
    if (!existsSync(settingsPath)) { 
      return defaultSettings
    }
    const json = await readFile(settingsPath, 'utf8')
    const { baseUrl, personalAccessToken, fetchInterval } = JSON.parse(json)
    return {
      ...defaultSettings,
      baseUrl,
      personalAccessToken: decrypt(personalAccessToken),
      fetchInterval
    }
  }
}

Actual Behavior

safeStorage.encryptString(plainText) works as expected but whenever a safeStorage.decryptString(buffer) is called it throws an error:

Error while decrypting the ciphertext provided to safeStorage.decryptString.

Testcase Gist URL

No response

Additional Information

No response

codebytere commented 2 years ago

Thanks for reporting this and helping to make Electron better!

Because of time constraints, triaging code with third-party dependencies is usually not feasible for a small team like Electron's.

Would it be possible for you to make a standalone testcase with only the code necessary to reproduce the issue? For example, Electron Fiddle is a great tool for making small test cases and makes it easy to publish your test case to a gist that Electron maintainers can use.

Stand-alone test cases make fixing issues go more smoothly: it ensure everyone's looking at the same issue, it removes all unnecessary variables from the equation, and it can also provide the basis for automated regression tests.

I'm adding the blocked/need-repro label for this reason. After you make a test case, please link to it in a followup comment. This issue will be closed in 10 days if the above is not addressed.

codebytere commented 2 years ago

We haven't gotten a response to our questions in our comment above. With only the information that is currently in the issue, we don't have enough information to take action. In this event, i'm going to go ahead and close this but can reopen should you follow up with more info!

mbeissinger commented 2 years ago

@pwasem I'm seeing the same issue -- did you find a solution?

pwasem commented 2 years ago

As I was not able to provide a proper setup to reproduce the issue it got closed. But I did not find a solution yet and the issue still persists.

general-ice commented 1 year ago

Try to use safeStorage after any browserWindow created. It helped me to figure out with the same error on MacOs.

kristof-siket commented 1 year ago

Some of the users of our Electron app are also facing this issue on Ubuntu 22.04. What we are doing:

We use safeStorage.encryptString to encrypt a token, throwing the following error:

Error while decrypting the ciphertext provided to safeStorage.decryptString. Encryption is not available.

The official documentation says that:

On Linux, returns true if the app has emitted the ready event and the secret key is available.

We are pretty sure that the app ready event has been emitted at that time, because we trigger this encryption connected to a UI action (user logs in, token is retrieved, we store it using safeStorage. So I'd say the other prerequisite "...and the secret key is available" should be the one the user does not meet.

What is the exact dependency of the safeStorage on Electron? Should something be configured in advance? Only some users have this issue, we didn't meet it during testing the app.

Thanks in advance if anyone has some advice.

barbalex commented 1 year ago

I have this issue too. In my case it depends on the windows user.

When running the decryption under my normal user everything works fine, dev and production. The code is called after the ready event.

When a different user runs it (even on the same windows machine), the error returned is: "Error while decrypting the ciphertext provided to safeStorage.decryptString".

But the value provided to safeStorage.decryptString(dbKey) is the exact same. And in both cases const isEncryptionAvailable = safeStorage.isEncryptionAvailable(), run right before, is true.

I can only imagine that safeStorage depends on something that is present in my dev user. But not in regular users, not doing development work.

By the way: I am seing the error even without dev setup because I am sending it from the production code using new Notification.

I dont think an electron fiddle would be of any help when user accounts have this influence. Maybe this issue should be re-opened.

MarshallOfSound commented 1 year ago

I have this issue too. In my case it depends on the windows user.

This is how the API is supposed to work, encrypting a string on one user and decrypting it on another will not work.

The encryption on all platforms is based on user backed credentials, it would be wildly insecure if the scenario you described was possible

barbalex commented 1 year ago

@MarshallOfSound thanks for that great advice

dan-cooke commented 1 year ago

I am encountering this error on Arch Linux, as @kristof-siket mentioned - this is likely due to this constraint:

On Linux, returns true if the app has emitted the ready event and the secret key is available.

What password manager is electron looking for exactly? I am using pass and it is configured with GPG secret key

MarshallOfSound commented 1 year ago

What password manager is electron looking for exactly?

* `basic_text` - When the desktop environment is not recognised or if the following
command line flag is provided `--password-store="basic"`.
* `gnome_libsecret` - When the desktop environment is `X-Cinnamon`, `Deepin`, `GNOME`, `Pantheon`, `XFCE`, `UKUI`, `unity` or if the following command line flag is provided `--password-store="gnome-libsecret"`.
* `kwallet` - When the desktop session is `kde4` or if the following command line flag
is provided `--password-store="kwallet"`.
* `kwallet5` - When the desktop session is `kde5` or if the following command line flag
is provided `--password-store="kwallet5"`.
* `kwallet6` - When the desktop session is `kde6`.
* `unknown` - When the function is called before app has emitted the `ready` event.
dan-cooke commented 1 year ago

Amazing @MarshallOfSound where is this documented?

I’m not fully understanding the basic_text option, that is the option I am likely triggering as I am not using a DE

MarshallOfSound commented 1 year ago

https://github.com/electron/electron/blob/main/docs/api/safe-storage.md#safestoragegetselectedstoragebackend-linux

dan-cooke commented 1 year ago

@MarshallOfSound thanks for your swift replies. but I'm not fully understanding this documentation

I am encountering the issue from OP on Arch Linux running Lightdm, so I do not have any of the KDE or gnome password managers that electron is looking for.

What exactly is basic_text ? And how do I configure this to allow safeStorage to work on my OS? It does not seem to work even when passing the cmd option

ilya-lopukhin commented 1 year ago

I'm experiencing this issue on Windows 10, inside a packaged distributed app - some users are facing trouble decrypting the very first string which was successfully encrypted during the same runtime. After rebooting PC / reinstalling the App the issue can sporadically solve itself. It seems like a problem with a host Windows system rather than an electron problem itself. In a development build - I faced even wilder issues, where rebuilding electron, or jumpstarting the safe storage with an immediate dummy "test" string encrypt/decrypt right away cleared an error for the rest of the operations

There are 2 crazy scenarios which I was unable to understand or debug properly yet.

var password = safeStorage.decryptString(fs.readFileSync(pathToEncryptedPassword)) // Fails with subject error

<rebuild electron>

var password = safeStorage.decryptString(fs.readFileSync(pathToEncryptedPassword)) // returns password
var password = safeStorage.decryptString(fs.readFileSync(pathToEncryptedPassword)) // Fails with subject error

var testEncrypted = safeStorage.encryptString('test')
var testDecrypted = safeStorage.decryptString(testEncrypted) // returns "test"

var password = safeStorage.decryptString(fs.readFileSync(pathToEncryptedPassword)) // suddenly returns password without an error
slhck commented 1 year ago

Does this potentially have to do with saving the encrypted string to a file, e.g. using electron-store? In my case I can run this part correctly:

var testEncrypted = safeStorage.encryptString('test')
var testDecrypted = safeStorage.decryptString(testEncrypted) // returns "test"

But once I round-trip through electron-store, it no longer is able to decrypt the string.

weirdal3333 commented 1 year ago

I have suddenly started having this issue on Windows 10 in multiple electron applications :(

param007 commented 1 year ago

Some of the users of our Electron app are also facing this issue on Ubuntu 22.04. What we are doing:

We use safeStorage.encryptString to encrypt a token, throwing the following error:

Error while decrypting the ciphertext provided to safeStorage.decryptString. Encryption is not available.

The official documentation says that:

On Linux, returns true if the app has emitted the ready event and the secret key is available.

We are pretty sure that the app ready event has been emitted at that time, because we trigger this encryption connected to a UI action (user logs in, token is retrieved, we store it using safeStorage. So I'd say the other prerequisite "...and the secret key is available" should be the one the user does not meet.

What is the exact dependency of the safeStorage on Electron? Should something be configured in advance? Only some users have this issue, we didn't meet it during testing the app.

Thanks in advance if anyone has some advice.

@codebytere @MarshallOfSound I ran into the same issue, can anyone update what does "the secret key is available" means here ? Same code is working perfectly fine on windows but not on ubuntu 22.04

ilya-lopukhin commented 1 year ago

I've just experienced this issue with a previously working production build of Pritunl OpenVPN client (which is made on top of electron as well). This issue is real but reproduction seems to be vague and bound to host Windows environment which is somehow preventing access to the DPAPI key for electron clients. Would be great to understand why it is happening and handle it properly

weirdal3333 commented 1 year ago

I've bee able to reliably reproduce this issue by just changing the windows user account password. Something is relying on the user account keystore, and it shouldn't be.

lawilog commented 1 year ago

I can no longer use the "Thunder Client" v2.11.3 extension of VSCodium 1.81.1 (uses Electron 22.3.18) under Ubuntu 22.04.3 LTS due to this error.

ugur-danis commented 10 months ago

Try to use safeStorage after any browserWindow created. It helped me to figure out with the same error on MacOs.

Thank you, it solved my problem.

SinusAlphaGames commented 7 months ago

Does this potentially have to do with saving the encrypted string to a file, e.g. using electron-store? In my case I can run this part correctly:

var testEncrypted = safeStorage.encryptString('test')
var testDecrypted = safeStorage.decryptString(testEncrypted) // returns "test"

But once I round-trip through electron-store, it no longer is able to decrypt the string.

I resolved this issue by saving encrypted string with base64 encoding (toString has utf8 encoding by default, and with it not working for me, too)

   configStore.set('secret', safeStorage.encryptString("test").toString('base64'));

    const secret : string = configStore.get('secret') as string;
    console.log('decrypted data: ' + safeStorage.decryptString(Buffer.from(secret, 'base64')));
   //print decrypted data: test