Can a WebAssembly App hold a secret? I thinking ...
# webassembly
s
Can a WebAssembly App hold a secret? I thinking about including API keys & credentials in the WASM file like I do with the apps for other platforms. The definition will be in Kotlin code as a constant. Can this be extracted somehow or is it (considerably) safe?
1
e
if it's on the client, it can be extracted, whether it's WASM or Android or iOS or anything
🤝 1
s
Yes, with some effort, I know.
But is it as hard as getting it from an Android or iOS app?
e
it's not hard to get them from an Android or iOS app
🤝 1
it is similarly easy in the constant pool
i
strings generally don't require any special tools to extract from binaries, simple hex editor is often more then enough.
s
Inspecting the constant pool requires some effort and knowledge. If it’s with WASM on the same level as for JVM, Android & iOS apps this is fine for me. Just wanted to know if those values get somewhere written in plain text where they are really easy reachable for everyone.
But this reminds me of a Java technique/recommendation saving them as char array instead of a String. What would you do to secure your secrets within the client app? 🤔
i
you can't, best you can do is obfuscate
s
Like saving the string encrypted and decrypt it at runtime using another one of the strings?
i
just as an example, here is the youtube apk
👍 1
you can encrypt sure but there is really no point and that is just obfuscation
If you give it to a client, it's not a secret. Period.
s
Can Kotlin/WASM be obfuscated? For the other platforms I use ProGuard and my ONEDRIVE_API becomes just A
Ok, but people will need to download the WASM file and inspect it with a HEX editor to get it, right? The can’t just open something with notepad or show source code and see it in their browser like they would with pure JS code, or can they?
Not looking for high security, I just don’t want to expose my secrets by accident to everyone and their dog 😅
i
The people you need to keep them from are the ones that won't be thwarted by simple obfuscation techniques
s
From your answer I assume that someone needs to know what a HEX editor is to get the secret. That’s safe enough for now as I don’t expect to be attacked. The secret should not just be presented to every user that accidentally opens the Firefox developer console. 😄 So I got my answer. Should be safe enough to bake it in. Thank you. 🙏🏻
e
They don't need a hex editor, just the
strings
program which is built into macos
s
But first they need to download the binary WASM file similar to how they need to get the APK or IPA.
e
that is visible in devtools
👆 1
c
@Stefan Oltmann Those who do not even know what an API key is, are not your “issue” 😉 the other ones, which will use the key to do bad things with it, are your issue.
s
I am lacking alternatives to store the secrets. Take the OneDrive API key for example that Ashampoo Photos uses to connect users to their OneDrive accounts. The communication solely happens between the client and OneDrive without interference. I think that would not generate trust if people see an unexpected communication happen in their desktop firewall. Or the Sentry API key… the app logs directly to Sentry. I feel there is sometimes no real „connect a server that can keep the secret for you“ routes.
@Stefan Oltmann Those who do not even know what an API key is, are not your “issue” 😉 the other ones, which will use the key to do bad things with it, are your issue.
For a side project I plan to include OneDrive credentials to let users submit files. I don’t expect real hackers, but maybe just some trolls that look if they can quickly see login credentials. 👀
c
but thats when obfuscation comes onto play. use a simple XOR of the API key to not have it obviously in the
strings
https://md5decrypt.net/en/Xor/
👍 2
🙏 2
s
💡 Wasn’t aware of that 😅 Sounds like a great idea and enough security for my use cases. 🙏🏻
c
👍
e
It depends what the secrets grant access to
Sentry key: assuming it's write only, worst case somebody can spam your logs. not a big deal
OneDrive: if it's only for reading public files, worst case an attacker can use your API quota
if it's for writing then I do not recommend embedding it in your binary, obfuscated or not
s
Strings are currently encoded in UTF-16, making them a tiny bit less convenient to extract, compared to ASCII/UTF-8.
👍 1
p
You can save the apiKey encrypted and decrypt it before use. The hacker can still see the decryption function and the key used to decrypt but it gives it more hurdles
👍 1
i
Turns out chrome is all you need to defeat all of these obfuscation ideas. Do not give clients legitimate secrets https://developer.chrome.com/blog/wasm-debugging-2020#memory-inspector
☝️ 1
👀 1
e
API keys will be visible in network inspector too
🤝 1
s
Oh, wow. With that I consider my question completely answered. 😅
Hm... ok... The XOR "decryption" can lead to non-printable chars. This makes it hard to include them as a GitHub secret. I think I could base64 encode them, but that would reveal the API key again in HEX view.
c
Base64 encode the obfuscated bytes. That’s what base64 is usually used for - encode bytes as a string 😉
s
How can I put a secret API as a String in my GitHub Secrets, but have it end up as bytes in my tool? 🤔 Right now I'm thinking about generated Kotlin code that writes a "val myBytes = byteArrayOf(...)" thingy
c
encoder:
Copy code
val byteArray :ByteArray = Xor.encode("MyAPI key")
val base64Encoded :String = Base64.encode(byteArray)
you can put the
base64Encoded
string as a secret in github actoins decoder:
Copy code
val byteArray Base64.decode(base64encoded)
val myApikey = Xor.decode(byteArray)
s
Oh, yes, sounds reasonable
c
so actually on your app you only need the “decode”, the encodig should happen upfront via CLI or so.
heres an implementation of XOR I usually use:
Copy code
package com.cmgapps.utils

import kotlin.experimental.xor
import kotlin.jvm.JvmStatic

object Xor {

    @JvmStatic
    fun xor(text: ByteArray?, key: ByteArray?): ByteArray =
        if (text == null || key == null || key.isEmpty()) {
            ByteArray(0)
        } else {
            val keyLength = key.size
            ByteArray(text.size).apply {
                text.forEachIndexed { i, byte ->
                    set(i, byte xor key[i % keyLength])
                }
            }
        }
}
🙏 1
👍 1
e
I wouldn't even bother. it's either going to be easily visible anyway, in which case save your effort and make it easier for yourself to debug, or it's important enough that you need better mechanisms for protection
p
So basically the only way is creating login credentials?
s
@ephemient And what is a better mechanism in your view?
e
depends
I don't know about OneDrive, but the recommended flow for S3 and GCS file uploads go like this. the client should communicate with your backend to receive an pre-signed upload url, which is only good for single file upload. your backend can look at various factors such as whether the user has suspicious activities before handing it out, and the client never has a secret which is usable beyond a very limited scope
s
And if you have no backend? My app is a client only photo app that directly connects with OneDrive & Dropbox.
While I agree that it’s safer for me to have an intermediate Ashampoo service handle this it’s less safe for the user. The user must trust that the service is not saving his access & refresh tokens. Of course we would never do that, but the user wouldn’t know. And maybe some attacker takes over such an server in the middle some day. People with personal desktop firewalls (for example Glasswire) might not expect such a connection and it does not help to build up trust.
The XOR way is the best thing I heard so far to decrease our risk while not increasing the risk for the user. I’m always open to hear more ideas.
e
what are the secrets actually being used for?
if it's something like an OAuth client id/secret, then whatever. it doesn't grant access to anything beyond the user, and not even other apps bother protecting them (e.g. twitter's API keys are widely known)
if it's something that other users shouldn't be able to access, then you can't go with your current architecture
s
My secrets are API keys for OneDrive, Dropbox and so on. A malicious person could steal them and use them for their own app. I guess the worst thing that may happen is that we can’t use OneDrive & Dropbox API anymore after keys got invalidated. Other secrets are for Google Analytics & Sentry. Attackers could spam our database there with fake data or use up our quotas. Don’t know how likely that is.
e
in that case I would say there's no value in trying to protect them. anybody who cares will get past your defenses anyways, and the only downside is you have to rotate your keys and deal with some spam
s
Initially for WASM I was thinking about including a supabase access token into the app for a pet project. With XOR I think protection of that is safe enough against the hex editor. Unfortunately I learned in the mean time that Ktor exceptions may dump those OAuth credentials (part of the header) into the logs. So this is really not an option and in this case I must indeed utilize a intermediate server. If that happens with Ashampoo Photos it’s just the user seeing his own access tokens, but in my pet project it would be my access token.
e
(in theory this is something that web attestation could help with, in allowing you to create keys that only work from your app, the way Google Play Services ensures only apps with your signature are allowed to use your Google API keys, … but we all know how well that proposal went over)
👍 1
c
in that case I would say there's no value in trying to protect them. anybody who cares will get past your defenses anyways, and the only downside is you have to rotate your keys and deal with some spam
But as harder as you make it, the more a hacker will lose interest. And that’s the whole point. It’s not hard to have the XOr implemented and it would require a hacker another step toward your api keys.
👍 2
e
it's trivially exposed in the network headers, XOR does nothing
s
As the saying goes „there is no security, you can just increase effort“
👍 2
it's trivially exposed in the network headers, XOR does nothing
XOR protects against the static hex editor analysis and requires the attacker to actually use the app and initiate such a network connection. That’s more effort and therefore not nothing.
e
it was easier to grab your API keys from network traffic than from source/disassembly to begin with. adding XOR to your code is about as useful as
no attacker is going to be analyzing your app without seeing how it runs
s
I‘m not in that business 🤷‍♂️ I hope to never attract people wanting to harm me - like Sony did. I remember well hassonybeenhackedtoday.com 😄