Skip to main content
1 of 3
mitchkman
  • 235
  • 3
  • 10

Generate key in Ruby, import in Geth

I'm a Ruby newbie and I need to implement a Geth-compatible key import/export in Ruby.

I was looking at the Geth implementation in Go, as well as keythereum.

This is what I have:

require "eth"
require 'fileutils'
require 'json'
require 'openssl'
require 'securerandom'
require 'test/unit'
 
class SimpleTest < Test::Unit::TestCase
 
  def test_create_keys

    # hardcoded password
    password = "password"

    # create new keypair using https://github.com/se3000/ruby-eth
        keypair = Eth::Key.new

        # key version
        version = 3

        # Generate key uuid v4
        uuid = SecureRandom.uuid

        # address
        address = keypair.to_address

        # more on aes-128-ctr:
        # https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29
        cipher_name = "aes-128-ctr"
        cipher = OpenSSL::Cipher.new cipher_name

        # iv "Initialization Vector"
        iv = SecureRandom.random_bytes(16) #store this with the generated value

        # Derive encryption key from password
        # kdf = key derivation function, for password-based keys
        digest_name = "sha256"
        prf = "hmac-" + digest_name
        digest = OpenSSL::Digest.new digest_name
        key_length = 32
        salt_length = 32
        iterations = 65536
        salt = SecureRandom.random_bytes(salt_length) #store this with the generated value
        derived_key = OpenSSL::PKCS5.pbkdf2_hmac(password, salt, iterations, key_length, digest)

        # Encrypt private key with key derived from password
        cipher.encrypt
        cipher.iv = iv
        iv = cipher.random_iv
        cipher.key = derived_key
        encrypted = cipher.update([keypair.private_key.to_hex].pack("H*")) + cipher.final

        # Generate mac
        derived_key_hex = derived_key.unpack("H*")[0]
        encrypted_hex = encrypted.unpack("H*")[0]
        mac = Eth::Utils.keccak256(derived_key_hex[32, 64] + encrypted)


        json = {
            "address" => address,
            "crypto" => {
                "cipher" => cipher_name,
                "ciphertext" => encrypted_hex,
                "cipherparams" => {
                    "iv" => iv.unpack("H*")[0]
                },
                "mac" => mac.unpack("H*")[0],
                "kdf" => "pbkdf2",
                "kdfparams" => {
                    "c" => iterations,
                    "dklen" => key_length, 
                    "prf" => prf, 
                    "salt" => salt.unpack("H*")[0], 
                }
            }, 
            "id": uuid,
            "version": version
        }

        file_name = "UTC--" + Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.%LZ") + "--" + address

        FileUtils.mkdir_p 'keystore'
        File.open("keystore/" + file_name,"w") do |f|
            f.write(json.to_json)
        end
  end
 
end

In Geth, I see my generated address, but I can't unlock the account with the password I have used.

> personal.listAccounts
["0xf31081983dcf4984304c14563f82ab29a630144d"]
> personal.unlockAccount("0xf31081983dcf4984304c14563f82ab29a630144d")
Unlock account 0xf31081983dcf4984304c14563f82ab29a630144d
Passphrase:
could not decrypt key with given passphrase

Something wrong in my implementation, but I can't find the issue.

mitchkman
  • 235
  • 3
  • 10