Search code examples
gocryptographypbkdf2

MetaMask Vault Decryptor in Golang


I'm trying out a way to decrypt a MetaMask wallet. MetaMask has its own online tool that provides a way to do this: https://metamask.github.io/vault-decryptor/

I found a Python library that performs this hashdecrypt task: https://pastebin.com/ncREzJSA

I'm trying to implement this code in Golang, but I get an empty result. I hope someone can help and explain what I did wrong.

JSON Wallet:

{"data":"rERJJccSBNfPzRHk32JxDQkpwV7bnMk3PULFnQz8DjKkGiFfmW81ED4dJAG0HHGfIy294AnJYxr7MG9IsVgS0bPD6yVBsfqw40mcsNuBIQLmNXcsw+zCxCnjbqGy2zf5pzvdt76mb4CHtTc7A2UhjApjWaiptR/rxosjkjHUrms0g5kGjr1gWzyZKwI/7u846WUQRRa1PNFX+qnQXWcCAXtjqIJrf762d5+a4rB8bCW3awHHkQm8kroH6PgiX3mYiRZUbDGUSD0DKIZCfHRj6BsndimeNYB0VfX82x0yzoS9D0fagOg1/ETn9cYNplIWT8L39Z9PBxueOOzfcRnQV/SEMozqiHk2SUC0Y1HhoXk4/L1WKF9pJdrCklP6zKaf7RTlXTFqOYkueEf/LGlPQ8MWeq4Nm1zADgJwWiSVWx5Jl6UrOvxNLTd0C4ZPFdunVVPkzYjzBVLgOYrE3udhWF+wqfJQZ6BXu8N5Y1bdoZEtKJ+SPuc+aVBd5Esz0q+mY/WIJswO5laY","iv":"xPb05MBtb23xPtfWxgCLzQ==","salt":"ggTRTywqaFJGxYAj73woAkA6mkjkY6q779u63DKLr3g="}

Password: tester123

You can try to decipher it here: https://metamask.github.io/vault-decryptor/

My Code

package main

import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/sha256"
    "encoding/base64"
    "encoding/json"
    "fmt"
    "golang.org/x/crypto/pbkdf2"
)

type Payload struct {
    Data string `json:"data"`
    Salt string `json:"salt"`
    Iv   string `json:"iv"`
}

var wallet = []byte(`{"data":"rERJJccSBNfPzRHk32JxDQkpwV7bnMk3PULFnQz8DjKkGiFfmW81ED4dJAG0HHGfIy294AnJYxr7MG9IsVgS0bPD6yVBsfqw40mcsNuBIQLmNXcsw+zCxCnjbqGy2zf5pzvdt76mb4CHtTc7A2UhjApjWaiptR/rxosjkjHUrms0g5kGjr1gWzyZKwI/7u846WUQRRa1PNFX+qnQXWcCAXtjqIJrf762d5+a4rB8bCW3awHHkQm8kroH6PgiX3mYiRZUbDGUSD0DKIZCfHRj6BsndimeNYB0VfX82x0yzoS9D0fagOg1/ETn9cYNplIWT8L39Z9PBxueOOzfcRnQV/SEMozqiHk2SUC0Y1HhoXk4/L1WKF9pJdrCklP6zKaf7RTlXTFqOYkueEf/LGlPQ8MWeq4Nm1zADgJwWiSVWx5Jl6UrOvxNLTd0C4ZPFdunVVPkzYjzBVLgOYrE3udhWF+wqfJQZ6BXu8N5Y1bdoZEtKJ+SPuc+aVBd5Esz0q+mY/WIJswO5laY","iv":"xPb05MBtb23xPtfWxgCLzQ==","salt":"ggTRTywqaFJGxYAj73woAkA6mkjkY6q779u63DKLr3g="}`)

func main() {
    var payload Payload
    json.Unmarshal(wallet, &payload)

    iv, _ := base64.StdEncoding.DecodeString(payload.Iv)
    salt, _ := base64.StdEncoding.DecodeString(payload.Salt)
    data, _ := base64.StdEncoding.DecodeString(payload.Data)

    // Remove last 16 symbols, this was done in the hashdecrypt python library
    data = data[:len(data)-len(iv)]

    password := "tester123"

    key := pbkdf2.Key([]byte(password), salt, 1000, 32, sha256.New)
    block, _ := aes.NewCipher(key)

    // In MetaMask's vault-decryptor, the nonce (initialization vector) size is fixed at 16 bytes.
    gcm, _ := cipher.NewGCMWithNonceSize(block, len(iv))
    plaintext, err := gcm.Open(nil, iv, data, nil)

    fmt.Println(err)
    fmt.Println(plaintext)
}

Result

cipher: message authentication failed
[]

Tried different variations, but no way to resolve it. I hope someone can help.


Solution

  • Whoops! My inattention...

    The mistake was here:

    key := pbkdf2.Key([]byte(password), salt, 1000, 32, sha256.New)
    

    Missed a single zero. It has to be:

    key := pbkdf2.Key([]byte(password), salt, 10000, 32, sha256.New)
    

    Also, this line must be deleted:

    data = data[:len(data)-len(iv)]
    

    Final working code

    package main
    
    import (
        "crypto/aes"
        "crypto/cipher"
        "crypto/sha256"
        "encoding/base64"
        "encoding/json"
        "fmt"
        "golang.org/x/crypto/pbkdf2"
    )
    
    type Payload struct {
        Data string `json:"data"`
        Salt string `json:"salt"`
        Iv   string `json:"iv"`
    }
    
    type Vault struct {
        Type string `json:"type"`
        Data struct {
            Mnemonic         []byte `json:"mnemonic"`
            NumberOfAccounts int    `json:"numberOfAccounts"`
            HDPath           string `json:"hdPath"`
        } `json:"data"`
    }
    
    var wallet = []byte(`{"data":"rERJJccSBNfPzRHk32JxDQkpwV7bnMk3PULFnQz8DjKkGiFfmW81ED4dJAG0HHGfIy294AnJYxr7MG9IsVgS0bPD6yVBsfqw40mcsNuBIQLmNXcsw+zCxCnjbqGy2zf5pzvdt76mb4CHtTc7A2UhjApjWaiptR/rxosjkjHUrms0g5kGjr1gWzyZKwI/7u846WUQRRa1PNFX+qnQXWcCAXtjqIJrf762d5+a4rB8bCW3awHHkQm8kroH6PgiX3mYiRZUbDGUSD0DKIZCfHRj6BsndimeNYB0VfX82x0yzoS9D0fagOg1/ETn9cYNplIWT8L39Z9PBxueOOzfcRnQV/SEMozqiHk2SUC0Y1HhoXk4/L1WKF9pJdrCklP6zKaf7RTlXTFqOYkueEf/LGlPQ8MWeq4Nm1zADgJwWiSVWx5Jl6UrOvxNLTd0C4ZPFdunVVPkzYjzBVLgOYrE3udhWF+wqfJQZ6BXu8N5Y1bdoZEtKJ+SPuc+aVBd5Esz0q+mY/WIJswO5laY","iv":"xPb05MBtb23xPtfWxgCLzQ==","salt":"ggTRTywqaFJGxYAj73woAkA6mkjkY6q779u63DKLr3g="}`)
    
    func main() {
        var payload Payload
        json.Unmarshal(wallet, &payload)
    
        iv, _ := base64.StdEncoding.DecodeString(payload.Iv)
        salt, _ := base64.StdEncoding.DecodeString(payload.Salt)
        data, _ := base64.StdEncoding.DecodeString(payload.Data)
    
        password := "tester123"
    
        key := pbkdf2.Key([]byte(password), salt, 10000, 32, sha256.New)
        block, _ := aes.NewCipher(key)
    
        // In MetaMask's vault-decryptor, the nonce (initialization vector) size is fixed at 16 bytes.
        gcm, _ := cipher.NewGCMWithNonceSize(block, len(iv))
        plaintext, err := gcm.Open(nil, iv, data, nil)
    
        if err != nil {
            panic(err)
        }
    
        var vault []Vault
        json.Unmarshal(plaintext, &vault)
        fmt.Println(string(vault[0].Data.Mnemonic))
    }
    

    The code is written as an example, so error checks have been ignored.