5

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. For generating the key, I am using the Gem https://github.com/se3000/ruby-eth. I verified with Geth that it generates the correct address for the private key.

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
    # This key generates address a8a659893d9d458d7c9ae88215c55f6a7cc561e6
        keypair = Eth::Key.new priv: "a0d10d8045ed900e74a0a662e288646569f12a1624efcaed0c639d1f6d09e993"

        # 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
["0xa8a659893d9d458d7c9ae88215c55f6a7cc561e6"]
> personal.unlockAccount("0xa8a659893d9d458d7c9ae88215c55f6a7cc561e6")
Unlock account 0xa8a659893d9d458d7c9ae88215c55f6a7cc561e6
Passphrase:
could not decrypt key with given passphrase

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

2

Just added this to the eth gem.

encrypted_key_json = Eth::Key.encrypt key, 'p455w0rD'

or

decrypted_key = Eth::Key.decrypt File.read('./some/path.json'), 'p455w0rD'

It doesn't write to disk like your code does, but it does handle the encryption and works with geth and myetherwallet. You can find the encryption code here, and the decryption code here.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.