Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
3 tasks done
pwasem opened this issue Jan 24, 2022 · 23 comments
Closed
3 tasks done

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

pwasem opened this issue Jan 24, 2022 · 23 comments
Labels

Comments

@pwasem
Copy link

pwasem commented Jan 24, 2022

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
Copy link
Member

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 codebytere added the blocked/need-repro Needs a test case to reproduce the bug label Jan 24, 2022
@codebytere
Copy link
Member

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
Copy link

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

@pwasem
Copy link
Author

pwasem commented Nov 11, 2022

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
Copy link

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

@kristof-siket
Copy link

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
Copy link

barbalex commented Feb 3, 2023

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
Copy link
Member

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
Copy link

barbalex commented Feb 3, 2023

@MarshallOfSound thanks for that great advice

@dan-cooke
Copy link

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

@github-actions github-actions bot removed the blocked/need-repro Needs a test case to reproduce the bug label Jul 24, 2023
@MarshallOfSound
Copy link
Member

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
Copy link

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
Copy link
Member

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

@dan-cooke
Copy link

@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
Copy link

ilya-lopukhin commented Jul 27, 2023

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
Copy link

slhck commented Aug 2, 2023

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
Copy link

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

@param007
Copy link

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
Copy link

ilya-lopukhin commented Aug 21, 2023

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
Copy link

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
Copy link

lawilog commented Sep 6, 2023

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
Copy link

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
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests