Bitcoin Forum
November 16, 2024, 01:10:23 AM *
News: Latest Bitcoin Core release: 28.0 [Torrent]
 
   Home   Help Search Login Register More  
Pages: [1]
  Print  
Author Topic: [PATCH] wallet private key encryption  (Read 8213 times)
jgarzik (OP)
Legendary
*
qt
Offline Offline

Activity: 1596
Merit: 1100


View Profile
March 26, 2011, 09:03:40 PM
Last edit: March 29, 2011, 06:07:31 PM by jgarzik
 #1

This patch adds one form of an opt-requested feature, wallet encryption:

     http://yyz.us/bitcoin/patch.bitcoin-wallet-crypter
     or git://github.com/jgarzik/bitcoin.git#crypter

BIG FAT WARNING: This patch is dangerous, and could create unspendable coins.  Do not use, unless you really know what you are doing.

Implementation details:

With this change, encryption (and associated passphrase) is now required, otherwise the program will exit.  The user may set their passphrase in the WALLET_PASSPHRASE environment variable, or in a GUI dialog if GUI is present.

A new type of wallet record, "ekey", is used to store a public key + AES-encrypted private key.  All new private keys are stored in "ekey" records.  Old keys stored in "key" and "wkey" records will continue to be read and supported indefinitely, but bitcoin no longer writes these records.

Caveats:

  • Totally untested rough draft.  It compiles and makes logical sense, that's about it.  I haven't even bothered to test it, though I welcome feedback from brave souls!
  • The implementation is bare-bones.  No provisions for wallet recovery, or detecting use of the wrong passphrase (easy way to corrupt!), etc.

Let the comments begin.  Hopefully this will spark discussion about the proper solution for wallet encryption, now that code is out there.

Jeff Garzik, Bloq CEO, former bitcoin core dev team; opinions are my own.
Visit bloq.com / metronome.io
Donations / tip jar: 1BrufViLKnSWtuWGkryPsKsxonV2NQ7Tcj
genjix
Legendary
*
expert
Offline Offline

Activity: 1232
Merit: 1076


View Profile
March 26, 2011, 09:18:58 PM
Last edit: March 27, 2011, 01:09:12 AM by genjix
 #2

I was actually also working on this using SSL's SEAL:

Code:
#include <string>
#include <exception>
using std::string;

//typedef struct RSA;
#include <openssl/rsa.h>

class ReadError : public std::exception
{
public:
    virtual const char* what() const throw();
};

class NoKeypairLoaded : public std::exception
{
public:
    virtual const char* what() const throw();
};

class AccessCard
{
public:
    AccessCard();
    ~AccessCard();
    void Generate();
    void Load(const string& pem, const string& pass);
    void PublicKey(string& pem) const;
    void PrivateKey(string& pem, const string& passphrase) const;

    void Seal(FILE* in_file, FILE* out_file);
    void Unseal(FILE* in_file, FILE* out_file);

private:
    void CheckKey() const;

    RSA* keypair;
};

class BioBox
{
public:
    struct Buffer
    {
        void* buf;
        int size;
    };

    BioBox();
    ~BioBox();
    void ConstructSink(const string& str);
    void NewBuffer();
    BIO* Bio() const;
    Buffer ReadAll();
private:
    BIO* bio;
    Buffer buf;
};

class EvpBox
{
public:
    EvpBox(RSA* keyp);
    ~EvpBox();
    EVP_PKEY* Key();
private:
    EVP_PKEY* evpkey;
};

//--------------------
#include <openssl/pem.h>

const char* ReadError::what() const throw()
{
    return "Problem reading BIO.";
}
const char* NoKeypairLoaded::what() const throw()
{
    return "No keypair loaded.";
}

AccessCard::AccessCard()
  : keypair(NULL)
{
    if (EVP_get_cipherbyname("aes-256-cbc") == NULL)
        OpenSSL_add_all_algorithms();
}
AccessCard::~AccessCard()
{
    RSA_free(keypair);
}
void AccessCard::CheckKey() const
{
    if (!keypair)
        throw NoKeypairLoaded();
}

void AccessCard::Generate()
{
    RSA_free(keypair);
    keypair = RSA_generate_key(2048, RSA_F4, NULL, NULL);
    CheckKey();
}

static char *LoseStringConst(const string& str)
{
    return const_cast<char*>(str.c_str());
}
static void* StringAsVoid(const string& str)
{
    return reinterpret_cast<void*>(LoseStringConst(str));
}

BioBox::BioBox()
 : bio(NULL)
{
    buf.buf = NULL;
    buf.size = 0;
}
BioBox::~BioBox()
{
    BIO_free(bio);
    free(buf.buf);
}
void BioBox::ConstructSink(const string& str)
{
    BIO_free(bio);
    bio = BIO_new_mem_buf(StringAsVoid(str), -1);
    if (!bio)
        throw ReadError();
}
void BioBox::NewBuffer()
{
    BIO_free(bio);
    bio = BIO_new(BIO_s_mem());
    if (!bio)
        throw ReadError();
}
BIO* BioBox::Bio() const
{
    return bio;
}
BioBox::Buffer BioBox::ReadAll()
{
    buf.size = BIO_ctrl_pending(bio);
    buf.buf = malloc(buf.size);
    if (BIO_read(bio, buf.buf, buf.size) < 0) {
        //if (ERR_peek_error()) {
        //    ERR_reason_error_string(ERR_get_error());
        //    return NULL;
        //}
        throw ReadError();
    }
    return buf;
}

EvpBox::EvpBox(RSA* keyp)
{
    evpkey = EVP_PKEY_new();
    if (!EVP_PKEY_set1_RSA(evpkey, keyp)) {
        throw ReadError();
    }
}
EvpBox::~EvpBox()
{
    EVP_PKEY_free(evpkey);
}
EVP_PKEY* EvpBox::Key()
{
    return evpkey;
}

static int pass_cb(char* buf, int size, int rwflag, void* u)
{
    const string pass = reinterpret_cast<char*>(u);
    int len = pass.size();
    // if too long, truncate
    if (len > size)
        len = size;
    pass.copy(buf, len);
    return len;
}
void AccessCard::Load(const string& pem, const string& pass)
{
    RSA_free(keypair);
    BioBox bio;
    bio.ConstructSink(pem);
    keypair = PEM_read_bio_RSAPrivateKey(bio.Bio(), NULL, pass_cb,
                                         StringAsVoid(pass));
    CheckKey();                    
}
void AccessCard::PublicKey(string& pem) const
{
    CheckKey();
    BioBox bio;
    bio.NewBuffer();
    int ret = PEM_write_bio_RSA_PUBKEY(bio.Bio(), keypair);
    if (!ret)
        throw ReadError();
    const BioBox::Buffer& buf = bio.ReadAll();
    pem = string(reinterpret_cast<const char*>(buf.buf), buf.size);
}

void AccessCard::PrivateKey(string& pem, const string& passphrase) const
{
    CheckKey();
    BioBox bio;
    bio.NewBuffer();

    EvpBox evp(keypair);
    int ret = PEM_write_bio_PKCS8PrivateKey(bio.Bio(), evp.Key(),
                                            EVP_aes_256_cbc(),
                                            LoseStringConst(passphrase),
                                            passphrase.size(), NULL, NULL);
    if (!ret)
        throw ReadError();
    const BioBox::Buffer& buf = bio.ReadAll();
    pem = string(reinterpret_cast<const char*>(buf.buf), buf.size);
}

#include <stdio.h>
#include <stdlib.h>

#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/err.h>

#include <arpa/inet.h> /* For htonl() */

void AccessCard::Seal(FILE* in_file, FILE* out_file)
{
    CheckKey();
    // see below
}
void AccessCard::Unseal(FILE* in_file, FILE* out_file)
{
    CheckKey();
}

//-------------------------------------------------------------------
// this wont be in the final file... it's our unit test
//-------------------------------------------------------------------
#include <iostream>
#include <assert.h>
using std::cout;

int main()
{
    AccessCard acc;                                                    
    // New key
    acc.Generate();
    string pem;
    //acc.PublicKey(pem);
    //cout << pem << "\n";
    acc.PrivateKey(pem, "hello");
    //cout << pem << "\n";
    //// ----------------
    acc.Load(pem, "hello");
    acc.PublicKey(pem);
    //cout << pem << "\n";
    //acc.Seal(stdin, stdout);
    return 0;
}
 

Code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/err.h>

#include <arpa/inet.h> /* For htonl() */

// rsa key should be 2048 bit with 65537 exponent
char* PUBLIC_KEY_PEM =
"-----BEGIN PUBLIC KEY-----\n"
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwu4ymKmuXoSvZn+TLOIh\n"
"SXq7w+P1OeM9LCDt7ZpR/krdMyW6UZBnZCNnB/1KWg+3dtO07ScSBgE0U/EMNvHB\n"
"Im/tvzhomZgMGtL0Ln+Lbh7UkdWqlBHgxgHzG6UX+Kr/jA+W1wR5mHIPZVBpx1Lq\n"
"ctSVf3EQhAHajyI3V+6JFDcdjzRPX9z5C/MCuHHhBhtjKfIE89dhZSyDvIFP/YFy\n"
"h/EeK7GiDcOrx+7hRZ6yfeD71xpK4lJ6GYw516+FaL1Yjdf/kRToDDziz00ZNEjg\n"
"gs7ps2D/8SKmUcsWRldbxm3+yKJPwUAv4jNAY/Aod7cs1q7f2YuPQC0WjriBR1s8\n"
"GwIDAQAB\n"
"-----END PUBLIC KEY-----\n";

char* PRIVATE_KEY_PEM =
"-----BEGIN ENCRYPTED PRIVATE KEY-----\n"
"MIIFHzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIa4XBjpJFZtQCAggA\n"
"MB0GCWCGSAFlAwQBKgQQ/dTrBC83CJnHfwgM6rotAwSCBNCvGXvzLgNkWT3Kvwui\n"
"Ln7gfiGtOfaubw5oq8DVh51hMhyYKnS8wAjMEkB0heFwTLLy1PCZnGv916NcUWhu\n"
"b4OxwCb/F3viBRSRgNaq5GB/c1HNeeg8CtEHs/EkxdIJuJuNr/giQp//TZ7Gm6Xf\n"
"Zb2rrdbW8ivxnactmJHdy5aRWA6Bhf5qr+0MjrTrUWKORUUIRLzJzGJ481Y0WIA6\n"
"9iK7I6aQnr5KzTETTUcMj+zU+WvlW3Ly3kn5ZGds/yB0wHnLTvCOBnIv9mq2nmZ3\n"
"Pyfm+WJirMwXkcZF+7U3Hsbk3Uk6VJqR/G0gKSiBebCgypYe6w5JTJQAoWdy96Zc\n"
"QHwr47dXIuhMzusdx9D67xz2HR0InGTnSTUOxCfYFDQaWdVUh2lHDswWqi6ZlFLJ\n"
"POkh24yaxE2S7Yn/CKAnET961GLAvVXChecjF2XkOoOGxE2rmOkrpc6fjndVTz5J\n"
"B4Vj0d3jotuHG+a6kbksV+71JbpJr5F69VRjsWpiEOwunHmtTdGhOmBkcyqJHXBh\n"
"8kusMSbZUzJTPFuTMJmxxzExasmif2CbJBl4WyfvKQjKlXyJSkysJN2V2NrA5Vpc\n"
"eb9msgXX8vBaN40ahT5mDeG5TXDPdssYOnQTM4vJo6IkSS2DIbMcUgbTcj9Qf3WZ\n"
"Y+FPROyIu8aKmCqDnDqJFp7aYaqjw5BWm6gL9YroPvNB8Funs8O1IgksMIUC+mt8\n"
"kFmq/Z9eUeoRawg4MGcMrgTdPDlrDB7uWfY2tPG/kkHQWEQyv13jJ1uEjUKf6wYk\n"
"WmIY5tvPEX3y4smscdWZGDQEjWL7zLs+7oLOwru2TbYmCx+V2QJpIW6Ppql/HCYr\n"
"+fJ+3qZ+I+eVcAW52jY8ygesab/+bQOrgpEfmBpTglTd+tt6h+16INagI4W+cY0z\n"
"iRcQjerjsgHM6JP4o4ZgxHBryHr5HdaZtpNXf3sHnaSwDCM/kVaeHAg+ciduRg+O\n"
"S6TIfdk334IFg7wXHRjjZXDX1QzRpUSBWvG4IdgE5Z9/3B0tvDbcU+R6c4GCTsiH\n"
"gZ/BWBQKaS3HhxhaUIlZhXXVgFvmGP5DsdaDQZG2VAsZqsTISrEG8OnHaklZIqi6\n"
"/GcMHJ2bHGmBKMgj0cTtkE53h1UZzlDkYqnf8b50uYunvDTNne/LPsPbfeuxTxDL\n"
"Oe5LtSD36vhSaExmaOkSN0qTY83zTDvxbSe7GBf87TNRimfnXZtxpghpgDffXgVi\n"
"j5JUcDjXpSC4xeu8jbq/lzh6x4o4nJRloPye8Zmq5VP7heZRN07GHDY6r1Jzz/4c\n"
"mBJWfMEg/OqB8CrOJibi6IPN3VkKzcF2WSWG712RizNHkWwaamlesC0dluXDmudj\n"
"bKr1niEF7KinUYiDu7ZJAntMRSm0hzjilAabSBXYP1RFOsZfgUfltON0oC2Zj5iX\n"
"wPaBUYhx2Fwn9jsge4NBWn3OXnqzZaq1Ojl0qb3YRbi2FVkisyZV/Dqzj1uGYkRq\n"
"6n6yoPqfAk/+u69Hzp1hHQi2wYbHbIDuQEI/kKRZ/XBxoL6c9LvYyKXKgBqd8RVU\n"
"T+AmBDkjEtksjQYaicwxe3MEcrM1PcEDyxJwRDsWp8Lo8wOU30A6ESlZb2TH/GgV\n"
"OxKP46YqccVN57+pU8RZ6bi0jg==\n"
"-----END ENCRYPTED PRIVATE KEY-----\n";

char* PRIVATE_KEY_PEM_TWO =
"-----BEGIN RSA PRIVATE KEY-----\n"
"MIIEowIBAAKCAQEAwu4ymKmuXoSvZn+TLOIhSXq7w+P1OeM9LCDt7ZpR/krdMyW6\n"
"UZBnZCNnB/1KWg+3dtO07ScSBgE0U/EMNvHBIm/tvzhomZgMGtL0Ln+Lbh7UkdWq\n"
"lBHgxgHzG6UX+Kr/jA+W1wR5mHIPZVBpx1LqctSVf3EQhAHajyI3V+6JFDcdjzRP\n"
"X9z5C/MCuHHhBhtjKfIE89dhZSyDvIFP/YFyh/EeK7GiDcOrx+7hRZ6yfeD71xpK\n"
"4lJ6GYw516+FaL1Yjdf/kRToDDziz00ZNEjggs7ps2D/8SKmUcsWRldbxm3+yKJP\n"
"wUAv4jNAY/Aod7cs1q7f2YuPQC0WjriBR1s8GwIDAQABAoIBACNlFRLbXKoU9bRq\n"
"3dJ8jQbGnmmHbvO/60+j+w/1wYWnGls7MoW07tEkDIVK3MFVsT6GWoflXERy24mS\n"
"b2FarHMQV98s4vFgxnHodCYtSqgIORjx7zNLu8C4geg3Jg8lHZnVCtKoIVwo+dqv\n"
"q0ViLOgE6dBmO1V88K6ky9/PlFxzBA7ER/XLqbkdmgDC+XLGQ63s31FXeKla9rQg\n"
"XodPx9AYUYuTLXOlNT1KXCa9a53qgB2woTGdtc1fPkBpnd+SHd2MItR+35X2ZXnY\n"
"b3kzOj+62O5MmC+sNysNy5D6Aut1fsEzauJr4jGP8TtpvC4Qc3/vLt2l7g/WHAzp\n"
"0TPiMGECgYEA9fHMBVIkTUzpKnzoF3jhqNFoqJqXgul32e7ej8Ov5b06wId1WLA/\n"
"/ZmgucPLAL1DDVyJMotNqeQPWt3x9++nTpnDHavGQ96y7SEI2zHuLMuhoR9c1zfA\n"
"MQicGfk25dhXWtMxFilFAkBbwCcLEq1XHcMI6ek4zKYlJ8tCHC4BmtcCgYEAyuZ0\n"
"91CBLcBuBrwpnxdXtOurwRDoOgXpJxy8bD9ctltcPFeilBUWuLYZ+gyBozli23Uh\n"
"KuDlKMZJw+CHztiEJwuzzzWOOxejs2qtBfSHM7Hne37Qpb/qcVfgn5C6fj7LeIgG\n"
"hUdK2QN2jEx+AWahFFFJ6Z2cmQBXXBGOW4LGZF0CgYA9+/ukV6hohvq4x5Qi3kdZ\n"
"KbXL0HJg/wBCv639457AMunMvhb4DCuEeaSFTPArtodgpbK6N1uSdrTb/NXP2+l5\n"
"qM0A/FrSnhzQIKQ/why503RfzCy03QsmEHpvHV0VnmmdrV5QrIQE5j15dx2WTnOH\n"
"P7FOaoXzJeh1WAfIXFvxLwKBgQC2XvIfIVMa5m1+zD2062xAB9w3CpVRIeLw7tlF\n"
"iqYwmmmLK1HMPDBSEgvDPt5+8aOzkdIgEkinn6LJ1tT6zI3r8o7J3l9bKeJP78BZ\n"
"K/MiOfPQgqnTcW6uNciGY7Xcp2CHk+wYe34BFSXG8TII3FBITNBclPgeZbof3P/R\n"
"rPfZWQKBgEbk1OsuDxUbcfgeMT0f1cBiJxtkRsj3Kffm6c2PAzGZg8A/EgoR25Cr\n"
"v+u4s5aS/3cBAfJhu9OAqoXdYJlXfYsoKuyOyvzeyOcsAsKjd6tIiU6xUe/u65oZ\n"
"1KXWzXoYgnNW3eoU7YeQ0Ca4SSg3qG2jqGFIAClla+hyLUeEgXB1\n"
"-----END RSA PRIVATE KEY-----\n";

static int pass_cb(char* buf, int size, int rwflag, void* u)
{
    int len = strlen("hello");
    // if too long, truncate
    if (len > size)
        len = size;
    strncpy(buf, "hello", len);
    return len;
}

int do_evp_seal(FILE *in_file, FILE *out_file)
{
    int retval = 0;
    RSA *rsa_pkey = NULL;
    EVP_PKEY *pkey = EVP_PKEY_new();
    EVP_CIPHER_CTX ctx;
    unsigned char buffer[4096];
    unsigned char buffer_out[4096 + EVP_MAX_IV_LENGTH];
    size_t len;
    int len_out;
    unsigned char *ek;
    int eklen;
    uint32_t eklen_n;
    unsigned char iv[EVP_MAX_IV_LENGTH];

    OpenSSL_add_all_algorithms();
    BIO* pBio = BIO_new_mem_buf(reinterpret_cast<void*>(PRIVATE_KEY_PEM), -1);
    //BIO* pBio = BIO_new_mem_buf(reinterpret_cast<void*>(PUBLIC_KEY_PEM), -1);
    //if (!PEM_read_bio_RSA_PUBKEY(pBio, &rsa_pkey, NULL, NULL))
    if (!PEM_read_bio_RSAPrivateKey(pBio, &rsa_pkey, pass_cb, NULL))
    //if (!PEM_read_bio_RSAPrivateKey(pBio, &rsa_pkey, NULL, NULL))
    {
        fprintf(stderr, "Error loading RSA Public Key File.\n");
        ERR_print_errors_fp(stderr);
        retval = 2;
        goto out;
    }

    if (!EVP_PKEY_assign_RSA(pkey, rsa_pkey))
    {
        fprintf(stderr, "EVP_PKEY_assign_RSA: failed.\n");
        retval = 3;
        goto out;
    }

    EVP_CIPHER_CTX_init(&ctx);
    ek = reinterpret_cast<unsigned char*>(malloc(EVP_PKEY_size(pkey)));

    if (!EVP_SealInit(&ctx, EVP_aes_256_cbc(), &ek, &eklen, iv, &pkey, 1))
    {
        fprintf(stderr, "EVP_SealInit: failed.\n");
        retval = 3;
        goto out_free;
    }

    /* First we write out the encrypted key length, then the encrypted key,
     * then the iv (the IV length is fixed by the cipher we have chosen).
     */

    eklen_n = htonl(eklen);
    if (fwrite(&eklen_n, sizeof eklen_n, 1, out_file) != 1)
    {
        perror("output file");
        retval = 5;
        goto out_free;
    }
    if (fwrite(ek, eklen, 1, out_file) != 1)
    {
        perror("output file");
        retval = 5;
        goto out_free;
    }
    if (fwrite(iv, EVP_CIPHER_iv_length(EVP_aes_256_cbc()), 1, out_file) != 1)
    {
        perror("output file");
        retval = 5;
        goto out_free;
    }

    /* Now we process the input file and write the encrypted data to the
     * output file. */

    while ((len = fread(buffer, 1, sizeof buffer, in_file)) > 0)
    {
        if (!EVP_SealUpdate(&ctx, buffer_out, &len_out, buffer, len))
        {
            fprintf(stderr, "EVP_SealUpdate: failed.\n");
            retval = 3;
            goto out_free;
        }

        if (fwrite(buffer_out, len_out, 1, out_file) != 1)
        {
            perror("output file");
            retval = 5;
            goto out_free;
        }
    }

    if (ferror(in_file))
    {
        perror("input file");
        retval = 4;
        goto out_free;
    }

    if (!EVP_SealFinal(&ctx, buffer_out, &len_out))
    {
        fprintf(stderr, "EVP_SealFinal: failed.\n");
        retval = 3;
        goto out_free;
    }

    if (fwrite(buffer_out, len_out, 1, out_file) != 1)
    {
        perror("output file");
        retval = 5;
        goto out_free;
    }

    out_free:
    EVP_PKEY_free(pkey);
    free(ek);

    out:
    return retval;
}

int main()
{
    return do_evp_seal(stdin, stdout);
}


Code:
#include <stdio.h>
#include <stdlib.h>

#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/err.h>

#include <arpa/inet.h>

char* PUBLIC_KEY_PEM =
"-----BEGIN PUBLIC KEY-----\n"
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvdbGZes3N/v3EqbbwYHW4rr4W\n"
"gav9eD36kVD7Hn5LIKwhfAqd1+LSN2YC49IsIhzwbXqoMUN89UZmFEdY4/M4qW1+\n"
"qwOfaX80EKCl8zV5Ztp8fvoErS1Z/G0k0c0kRdP0BCZTI9nSfjZkhbqWKnJrtCgi\n"
"s6OXXTeanU9B9Bz3HQIDAQAB\n"
"-----END PUBLIC KEY-----\n";

char* PRIVATE_KEY_PEM =
"-----BEGIN ENCRYPTED PRIVATE KEY-----\n"
"MIIFHzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIa4XBjpJFZtQCAggA\n"
"MB0GCWCGSAFlAwQBKgQQ/dTrBC83CJnHfwgM6rotAwSCBNCvGXvzLgNkWT3Kvwui\n"
"Ln7gfiGtOfaubw5oq8DVh51hMhyYKnS8wAjMEkB0heFwTLLy1PCZnGv916NcUWhu\n"
"b4OxwCb/F3viBRSRgNaq5GB/c1HNeeg8CtEHs/EkxdIJuJuNr/giQp//TZ7Gm6Xf\n"
"Zb2rrdbW8ivxnactmJHdy5aRWA6Bhf5qr+0MjrTrUWKORUUIRLzJzGJ481Y0WIA6\n"
"9iK7I6aQnr5KzTETTUcMj+zU+WvlW3Ly3kn5ZGds/yB0wHnLTvCOBnIv9mq2nmZ3\n"
"Pyfm+WJirMwXkcZF+7U3Hsbk3Uk6VJqR/G0gKSiBebCgypYe6w5JTJQAoWdy96Zc\n"
"QHwr47dXIuhMzusdx9D67xz2HR0InGTnSTUOxCfYFDQaWdVUh2lHDswWqi6ZlFLJ\n"
"POkh24yaxE2S7Yn/CKAnET961GLAvVXChecjF2XkOoOGxE2rmOkrpc6fjndVTz5J\n"
"B4Vj0d3jotuHG+a6kbksV+71JbpJr5F69VRjsWpiEOwunHmtTdGhOmBkcyqJHXBh\n"
"8kusMSbZUzJTPFuTMJmxxzExasmif2CbJBl4WyfvKQjKlXyJSkysJN2V2NrA5Vpc\n"
"eb9msgXX8vBaN40ahT5mDeG5TXDPdssYOnQTM4vJo6IkSS2DIbMcUgbTcj9Qf3WZ\n"
"Y+FPROyIu8aKmCqDnDqJFp7aYaqjw5BWm6gL9YroPvNB8Funs8O1IgksMIUC+mt8\n"
"kFmq/Z9eUeoRawg4MGcMrgTdPDlrDB7uWfY2tPG/kkHQWEQyv13jJ1uEjUKf6wYk\n"
"WmIY5tvPEX3y4smscdWZGDQEjWL7zLs+7oLOwru2TbYmCx+V2QJpIW6Ppql/HCYr\n"
"+fJ+3qZ+I+eVcAW52jY8ygesab/+bQOrgpEfmBpTglTd+tt6h+16INagI4W+cY0z\n"
"iRcQjerjsgHM6JP4o4ZgxHBryHr5HdaZtpNXf3sHnaSwDCM/kVaeHAg+ciduRg+O\n"
"S6TIfdk334IFg7wXHRjjZXDX1QzRpUSBWvG4IdgE5Z9/3B0tvDbcU+R6c4GCTsiH\n"
"gZ/BWBQKaS3HhxhaUIlZhXXVgFvmGP5DsdaDQZG2VAsZqsTISrEG8OnHaklZIqi6\n"
"/GcMHJ2bHGmBKMgj0cTtkE53h1UZzlDkYqnf8b50uYunvDTNne/LPsPbfeuxTxDL\n"
"Oe5LtSD36vhSaExmaOkSN0qTY83zTDvxbSe7GBf87TNRimfnXZtxpghpgDffXgVi\n"
"j5JUcDjXpSC4xeu8jbq/lzh6x4o4nJRloPye8Zmq5VP7heZRN07GHDY6r1Jzz/4c\n"
"mBJWfMEg/OqB8CrOJibi6IPN3VkKzcF2WSWG712RizNHkWwaamlesC0dluXDmudj\n"
"bKr1niEF7KinUYiDu7ZJAntMRSm0hzjilAabSBXYP1RFOsZfgUfltON0oC2Zj5iX\n"
"wPaBUYhx2Fwn9jsge4NBWn3OXnqzZaq1Ojl0qb3YRbi2FVkisyZV/Dqzj1uGYkRq\n"
"6n6yoPqfAk/+u69Hzp1hHQi2wYbHbIDuQEI/kKRZ/XBxoL6c9LvYyKXKgBqd8RVU\n"
"T+AmBDkjEtksjQYaicwxe3MEcrM1PcEDyxJwRDsWp8Lo8wOU30A6ESlZb2TH/GgV\n"
"OxKP46YqccVN57+pU8RZ6bi0jg==\n"
"-----END ENCRYPTED PRIVATE KEY-----\n";

char* PRIVATE_KEY_PEM_TWO =
"-----BEGIN RSA PRIVATE KEY-----\n"
"MIIEowIBAAKCAQEAwu4ymKmuXoSvZn+TLOIhSXq7w+P1OeM9LCDt7ZpR/krdMyW6\n"
"UZBnZCNnB/1KWg+3dtO07ScSBgE0U/EMNvHBIm/tvzhomZgMGtL0Ln+Lbh7UkdWq\n"
"lBHgxgHzG6UX+Kr/jA+W1wR5mHIPZVBpx1LqctSVf3EQhAHajyI3V+6JFDcdjzRP\n"
"X9z5C/MCuHHhBhtjKfIE89dhZSyDvIFP/YFyh/EeK7GiDcOrx+7hRZ6yfeD71xpK\n"
"4lJ6GYw516+FaL1Yjdf/kRToDDziz00ZNEjggs7ps2D/8SKmUcsWRldbxm3+yKJP\n"
"wUAv4jNAY/Aod7cs1q7f2YuPQC0WjriBR1s8GwIDAQABAoIBACNlFRLbXKoU9bRq\n"
"3dJ8jQbGnmmHbvO/60+j+w/1wYWnGls7MoW07tEkDIVK3MFVsT6GWoflXERy24mS\n"
"b2FarHMQV98s4vFgxnHodCYtSqgIORjx7zNLu8C4geg3Jg8lHZnVCtKoIVwo+dqv\n"
"q0ViLOgE6dBmO1V88K6ky9/PlFxzBA7ER/XLqbkdmgDC+XLGQ63s31FXeKla9rQg\n"
"XodPx9AYUYuTLXOlNT1KXCa9a53qgB2woTGdtc1fPkBpnd+SHd2MItR+35X2ZXnY\n"
"b3kzOj+62O5MmC+sNysNy5D6Aut1fsEzauJr4jGP8TtpvC4Qc3/vLt2l7g/WHAzp\n"
"0TPiMGECgYEA9fHMBVIkTUzpKnzoF3jhqNFoqJqXgul32e7ej8Ov5b06wId1WLA/\n"
"/ZmgucPLAL1DDVyJMotNqeQPWt3x9++nTpnDHavGQ96y7SEI2zHuLMuhoR9c1zfA\n"
"MQicGfk25dhXWtMxFilFAkBbwCcLEq1XHcMI6ek4zKYlJ8tCHC4BmtcCgYEAyuZ0\n"
"91CBLcBuBrwpnxdXtOurwRDoOgXpJxy8bD9ctltcPFeilBUWuLYZ+gyBozli23Uh\n"
"KuDlKMZJw+CHztiEJwuzzzWOOxejs2qtBfSHM7Hne37Qpb/qcVfgn5C6fj7LeIgG\n"
"hUdK2QN2jEx+AWahFFFJ6Z2cmQBXXBGOW4LGZF0CgYA9+/ukV6hohvq4x5Qi3kdZ\n"
"KbXL0HJg/wBCv639457AMunMvhb4DCuEeaSFTPArtodgpbK6N1uSdrTb/NXP2+l5\n"
"qM0A/FrSnhzQIKQ/why503RfzCy03QsmEHpvHV0VnmmdrV5QrIQE5j15dx2WTnOH\n"
"P7FOaoXzJeh1WAfIXFvxLwKBgQC2XvIfIVMa5m1+zD2062xAB9w3CpVRIeLw7tlF\n"
"iqYwmmmLK1HMPDBSEgvDPt5+8aOzkdIgEkinn6LJ1tT6zI3r8o7J3l9bKeJP78BZ\n"
"K/MiOfPQgqnTcW6uNciGY7Xcp2CHk+wYe34BFSXG8TII3FBITNBclPgeZbof3P/R\n"
"rPfZWQKBgEbk1OsuDxUbcfgeMT0f1cBiJxtkRsj3Kffm6c2PAzGZg8A/EgoR25Cr\n"
"v+u4s5aS/3cBAfJhu9OAqoXdYJlXfYsoKuyOyvzeyOcsAsKjd6tIiU6xUe/u65oZ\n"
"1KXWzXoYgnNW3eoU7YeQ0Ca4SSg3qG2jqGFIAClla+hyLUeEgXB1\n"
"-----END RSA PRIVATE KEY-----\n";

int do_evp_seal(FILE *in_file, FILE *out_file)
{
    int retval = 0;
    RSA *rsa_pkey = NULL;
    EVP_PKEY *pkey = EVP_PKEY_new();
    EVP_CIPHER_CTX ctx;
    unsigned char buffer[4096];
    unsigned char buffer_out[4096 + EVP_MAX_IV_LENGTH];
    size_t len;
    int len_out;
    unsigned char *ek;
    int eklen;
    uint32_t eklen_n;
    unsigned char iv[EVP_MAX_IV_LENGTH];

    OpenSSL_add_all_algorithms();
    BIO* pBio = BIO_new_mem_buf(reinterpret_cast<void*>(PRIVATE_KEY_PEM), -1);
    if (!PEM_read_bio_RSAPrivateKey(pBio, &rsa_pkey, NULL, NULL))
    {
        fprintf(stderr, "Error loading RSA Public Key File.\n");
        ERR_print_errors_fp(stderr);
        retval = 2;
        goto out;
    }

    if (!EVP_PKEY_assign_RSA(pkey, rsa_pkey))
    {
        fprintf(stderr, "EVP_PKEY_assign_RSA: failed.\n");
        retval = 3;
        goto out;
    }

    /* First we write out the encrypted key length, then the encrypted key,
     * then the iv (the IV length is fixed by the cipher we have chosen).
     */

    if (fread(&eklen_n, sizeof eklen_n, 1, in_file) != 1)
    {
        perror("input file");
        retval = 5;
        goto out_free;
    }
    eklen = ntohl(eklen_n);
    //eklen = 128;
    //ek = reinterpret_cast<unsigned char*>(malloc(EVP_PKEY_size(pkey)));
    //ek = reinterpret_cast<unsigned char*>(malloc(eklen));
    ek = new unsigned char[eklen];
    if (fread(ek, eklen, 1, in_file) != 1)
    {
        perror("input file");
        retval = 5;
        goto out_free;
    }
    if (fread(iv, EVP_CIPHER_iv_length(EVP_aes_256_cbc()), 1, in_file) != 1)
    {
        perror("input file");
        retval = 5;
        goto out_free;
    }

    EVP_CIPHER_CTX_init(&ctx);

    if (!EVP_OpenInit(&ctx, EVP_aes_256_cbc(), ek, eklen, iv, pkey))
    {
        fprintf(stderr, "EVP_OpenInit: failed.\n");
        retval = 3;
        goto out_free;
    }
    printf("%d\n", eklen);

    /* Now we process the input file and write the encrypted data to the
     * output file. */

    while ((len = fread(buffer, 1, sizeof buffer, in_file)) > 0)
    {
        if (!EVP_OpenUpdate(&ctx, buffer_out, &len_out, buffer, len))
        {
            fprintf(stderr, "EVP_OpenUpdate: failed.\n");
            retval = 3;
            goto out_free;
        }

        if (fwrite(buffer_out, len_out, 1, out_file) != 1)
        {
            perror("output file");
            retval = 5;
            goto out_free;
        }
    }

    if (ferror(in_file))
    {
        perror("input file");
        retval = 4;
        goto out_free;
    }

    if (!EVP_OpenFinal(&ctx, buffer_out, &len_out))
    {
        fprintf(stderr, "EVP_OpenFinal: failed.\n");
        retval = 3;
        goto out_free;
    }

    if (fwrite(buffer_out, len_out, 1, out_file) != 1)
    {
        perror("output file");
        retval = 5;
        goto out_free;
    }

    out_free:
    EVP_PKEY_free(pkey);
    free(ek);

    out:
    return retval;
}

int main(int argc, char *argv[])
{
    return do_evp_seal(stdin, stdout);
}



Not sure if that code is useful now, but dumping it anyway...

g++ a.cpp -lssl -o enctest
./enctest
g++ b.cpp -lssl -o seal
./seal < INPUT > ENCOUT
g++ c.cpp -lssl -o unseal
./unseal < ENCOUT
jgarzik (OP)
Legendary
*
qt
Offline Offline

Activity: 1596
Merit: 1100


View Profile
March 26, 2011, 09:48:51 PM
 #3

Not sure if that code is useful now, but dumping it anyway...

There's been a lot of feature-requesting and demoing and such.  By posting real code against current bitcoin, it was hoped that we could move on to a more focused discussion (w/ real code) about what wallet encryption should look like.

We need to answer questions such as

Is the presented method cryptographically strong?
Should we encrypt the entire wallet, and not just private keys?
Should we encrypt the plaintext data concatenated together as a single huge memory buffer, enhancing cipher block chaining, or encrypt invididual records as my patch does?
How to handle wallet upgrade + downgrade?  "ekey" creates unspendable coins, when moving to older clients.
How to check for invalid passphrase?  It seems cryptographically unwise to use the naive implementation (encrypt a static, well known string; check for invalid passphrase by attempting to decrypt to well known plaintext).


Jeff Garzik, Bloq CEO, former bitcoin core dev team; opinions are my own.
Visit bloq.com / metronome.io
Donations / tip jar: 1BrufViLKnSWtuWGkryPsKsxonV2NQ7Tcj
casascius
Mike Caldwell
VIP
Legendary
*
Offline Offline

Activity: 1386
Merit: 1140


The Casascius 1oz 10BTC Silver Round (w/ Gold B)


View Profile WWW
March 26, 2011, 10:00:36 PM
 #4

Should we encrypt the entire wallet, and not just private keys?

I would vote no - encrypt private keys only - otherwise passphrase must be typed more often, making it more vulnerable to interception.

Should we encrypt the plaintext data concatenated together as a single huge memory buffer, enhancing cipher block chaining, or encrypt invididual records as my patch does?

If each record were encrypted with a properly cryptographically random initialization vector, the two should be equivalent.  On the other hand, encrypting everything as a block guarantees that there's no possibility of different records being encrypted with different keys for whatever reason.

How to check for invalid passphrase?  It seems cryptographically unwise to use the naive implementation (encrypt a static, well known string; check for invalid passphrase by attempting to decrypt to well known plaintext).

Assuming the policy isn't "encrypt everything", wouldn't it be simple and straightforward to correlate the private key with either the public key, or the bitcoin address?  Seems either it would check out or it would not.  If the policy were encrypt everything, simply storing perhaps the last 16 bits of the hash of (password+salt), where this salt was only used for the password-verification hash, so that 16 bits of the actual key weren't being given away, you'd have a method that catches nearly all typos, without being very useful to a password cracker.


Companies claiming they got hacked and lost your coins sounds like fraud so perfect it could be called fashionable.  I never believe them.  If I ever experience the misfortune of a real intrusion, I declare I have been honest about the way I have managed the keys in Casascius Coins.  I maintain no ability to recover or reproduce the keys, not even under limitless duress or total intrusion.  Remember that trusting strangers with your coins without any recourse is, as a matter of principle, not a best practice.  Don't keep coins online. Use paper or hardware wallets instead.
genjix
Legendary
*
expert
Offline Offline

Activity: 1232
Merit: 1076


View Profile
March 27, 2011, 01:07:12 AM
 #5

Is the presented method cryptographically strong?
SSL recommends to use SEAL which does RSA + AES with IVs (as above). You shouldn't use constant IVs because that can lead to a statistical attack.

Quote
Should we encrypt the entire wallet, and not just private keys?
Entire wallet. Decrypt into memory once at startup. Randomise location in memory somehow.

Quote
How to check for invalid passphrase?  It seems cryptographically unwise to use the naive implementation (encrypt a static, well known string; check for invalid passphrase by attempting to decrypt to well known plaintext).

The above code I wrote, will try to load the PEM but throw an exception when an invalid passphrase is used. So the code is as simple as:

try {
  // load
} catch (ReadError err) {
}
Hal
VIP
Sr. Member
*
expert
Offline Offline

Activity: 314
Merit: 4276



View Profile
March 27, 2011, 01:08:37 AM
 #6

That's a very concise implementation. I did spot one bug:

Code:
+    vector<unsigned char> Encrypt(vector<unsigned char> vchPlaintext)
+    {
+      // max ciphertext len for a n bytes of plaintext is
+      // n + AES_BLOCK_SIZE - 1 bytes
+      int len = vchPlaintext.size();
+      int c_len = len + AES_BLOCK_SIZE, f_len = 0;
+      vector<unsigned char> vchCiphertext(c_len);
The max ciphertext size is actually len + 2*AES_BLOCK_SIZE, so you should set c_len to that, and allocate that much space.

Also a security flaw, you are using a constant IV everywhere, it looks like. You need to use a different IV for each encryption.

One other point, Bitcoin uses a CPrivKey type for sensitive data like private keys. It zeroes memory when it's freed.

Hal Finney
Hal
VIP
Sr. Member
*
expert
Offline Offline

Activity: 314
Merit: 4276



View Profile
March 27, 2011, 08:42:35 PM
 #7

There's good discussion of this feature here:

https://github.com/bitcoin/bitcoin/issues#issue/3

https://gist.github.com/803170

Issues:

 - Symmetric (aes) vs public key (rsa) encryption

 - Decrypt at startup vs decrypt on use

 - Create new keys automatically (as now) vs create only on user action

 - Encrypt all keys with same passphrase vs different passphrases for different key sets

One way to analyze these is via a threat model. What can the attacker do, that we will try to defend against? And what will we not defend against?

We can distinguish three levels of attacker:

1. Can read user files

2. Can read/write user files but only read system files

3. Can read/write everything, root privileges


Sorry, getting tired, will write more later.".

Hal Finney
devrandom
Newbie
*
Offline Offline

Activity: 26
Merit: 0



View Profile WWW
March 28, 2011, 05:30:55 AM
 #8

Issues:

 - Symmetric (aes) vs public key (rsa) encryption

 - Decrypt at startup vs decrypt on use

 - Create new keys automatically (as now) vs create only on user action

 - Encrypt all keys with same passphrase vs different passphrases for different key sets

A flexible system could do the following, with the user making some usability choices:

* Have the user choose a passphrase, and derive one or two secret keys OR the user chooses two passphrases
* Optionally encrypt the wallet as a whole with one secret key
* Always encrypt the private keys with the second secret key
* If the whole wallet is encrypted, prompt for passphrase on startup
* Optionally forget the passphrase after startup, while keeping the wallet secret key
* If passphrase was forgotten, or there is no whole-wallet encryption, and the user wishes to spend, prompt for the passphrase again
* Again, optionally forget the passphrase after spending

I think new keys can still be created automatically.  Just create them in batch ahead of time during spend operations.

Different passphrases for different key sets are an interesting idea, but usability would start to suffer due to complexity.  Might be better to have separate wallets.


We can distinguish three levels of attacker:

1. Can read user files

2. Can read/write user files but only read system files

3. Can read/write everything, root privileges


Sorry, getting tired, will write more later.".

I don't think there's any difference between #2 and #3.  In either case, the attacker can listen for the user's passphrase.
da2ce7
Legendary
*
Offline Offline

Activity: 1222
Merit: 1016


Live and Let Live


View Profile
March 28, 2011, 06:22:04 AM
 #9

There should be two layers of encryption.

The entire wallet should have (optional) AES encryption.

Nested inside this wallet package there should be a PGP private and public key.  This private key should be also encrypted with AES (but with a different passphase)

Every Bitcoin private key should be encrypted independently to the public key, so making a new Bitcoin address will not require entering the private key passphase.

When first loading, Bitcoin will ask for the AES wallet key, if there is one. (this step is optional)

Once the wallet is decrypted, the client will ask "How much funds do you want to make available for spending, and for how long?"

If the answer is more than 'none,'  the client will ask for the private key passphase, and decrypt the minimum number of bitcoin private keys required to make the funds available and store them only in memory.  The passphase and decrypted global private key will automatically be deleted (from memory) after this operation.

If the user wishes to unlock more funds, or wants to send funds after the unlocked time has expired, the Bitcoin client will ask for the private key passphase again.

One off NP-Hard.
jgarzik (OP)
Legendary
*
qt
Offline Offline

Activity: 1596
Merit: 1100


View Profile
March 28, 2011, 07:30:32 AM
Last edit: March 29, 2011, 06:41:08 PM by jgarzik
 #10

A setup that user might reasonably be expected to understand could be:  one password to unlock the wallet (whole-file), and a second to spend money (ECDSA private keys).

However...    It remains a PITA with the current bitcoin implementation to perform whole-file encryption on the wallet.  db4 has whole-environment encryption, so that implies an encrypt-everything solution would encrypt the wallet, addr and blkindex databases.

One could encrypt the 'value' part of each db4 key/value pair, by modifying bitcoin's database Read() and Write() methods.  But with the keys being plaintext, I'm not sure that's useful.

With the current implementation, you are locked into a lot of design decisions, where if you deviate you must rewrite a lot of code all over the codebase.

For that reason, I had hoped a patch would focus discussion on what is reasonable with the current codebase, rather than a perfect solution that will never see the light of day.


Jeff Garzik, Bloq CEO, former bitcoin core dev team; opinions are my own.
Visit bloq.com / metronome.io
Donations / tip jar: 1BrufViLKnSWtuWGkryPsKsxonV2NQ7Tcj
devrandom
Newbie
*
Offline Offline

Activity: 26
Merit: 0



View Profile WWW
March 29, 2011, 03:37:44 PM
 #11

However...    It remains a PITA with the current bitcoin implementation to perform whole-file encryption on the wallet.  db4 has whole-environment encryption, so that implies an encrypt-everything solution would encrypt the wallet, addr and blkindex databases.


It doesn't seem to hurt to encrypt everything?  I think symmetric encryption can do the entire block chain in about a second.

http://www.cryptopp.com/benchmarks.html
Gavin Andresen
Legendary
*
qt
Offline Offline

Activity: 1652
Merit: 2301


Chief Scientist


View Profile WWW
March 29, 2011, 04:05:25 PM
 #12

One way to analyze these is via a threat model. What can the attacker do, that we will try to defend against? And what will we not defend against?

We can distinguish three levels of attacker:

1. Can read user files

2. Can read/write user files but only read system files

3. Can read/write everything, root privileges

I think that's the right way to think about it.  And I think Jeff actually implementing a straw-man proposal is exactly the right thing to do.

So:  I say we don't try to defend against (3), at least not right now.  If you have root then you can install a keylogger, read memory, intercept any system call, etc etc etc.   (I would like to see somebody implement a bitcoin client that required payment verification using a cell phone app or telephone call or PIN-sent-to-email and did all the magic key management to make that work securely, but I think that's beyond the scope of what we can reasonably do right now).

Defending against (1) and (2) would help with:

a) you forget to logout so attacker sits down at your computer, starts bitcoin and empties your wallet.
b) attacker gets a hold of a filesystem backup that is not encrypted.
c) sysadmin sets file permissions incorrectly so attacker on multi-user system can read your wallet.dat
d) attacker guesses or finds out your ssh password, logs in remotely and steals your wallet.dat.

It won't help with:
- sysadmin with root privileges is evil
- system compromised by rootkit/trojan/keylogger


RE: encrypt everything:  I say maybe later.  Just encrypt everything isn't trivial: users would have to wait a minute or two or ten for Berkeley DB to rewrite all of blkindex.dat (bottleneck will be disk I/O, not the encryption), and we have to deal with "my disk filled up when I changed my password, things are half-encrypted and half-not, what do I do now?"   And I don't see a lot of value in encrypting all of wallet.dat; forget to shutdown bitcoin and an attacker that wants to know your public addresses can just open up the address book and take a screenshot.

How often do you get the chance to work on a potentially world-changing project?
jgarzik (OP)
Legendary
*
qt
Offline Offline

Activity: 1596
Merit: 1100


View Profile
March 29, 2011, 08:30:40 PM
 #13

Also a security flaw, you are using a constant IV everywhere, it looks like. You need to use a different IV for each encryption.

I'm not an AES expert, so permit a dumb question:  does AES decryption require knowledge of the IV used to encrypt a given ciphertext?

With the only information persisting between sessions being the user's passphrase, that would seem to imply storing the IV for each encryption?


Jeff Garzik, Bloq CEO, former bitcoin core dev team; opinions are my own.
Visit bloq.com / metronome.io
Donations / tip jar: 1BrufViLKnSWtuWGkryPsKsxonV2NQ7Tcj
devrandom
Newbie
*
Offline Offline

Activity: 26
Merit: 0



View Profile WWW
March 30, 2011, 03:52:14 AM
 #14

Also a security flaw, you are using a constant IV everywhere, it looks like. You need to use a different IV for each encryption.

I'm not an AES expert, so permit a dumb question:  does AES decryption require knowledge of the IV used to encrypt a given ciphertext?

With the only information persisting between sessions being the user's passphrase, that would seem to imply storing the IV for each encryption?


An IV is always transmitted before the ciphertext (or stored, in this case) and is necessary for decryption.  The IV's purpose is to start the encryption from a random state, so that encrypting the same plaintext twice does not result in the same ciphertext.  This prevents comparing two records that have the same initial segment.
genjix
Legendary
*
expert
Offline Offline

Activity: 1232
Merit: 1076


View Profile
March 30, 2011, 06:11:21 AM
 #15

If you manage to gather lots of IVs then you can use ones that are the same/similar to do a statistical attack to discover the secret key. This is how WEP is cracked because it's IV is only 24 bit, and that's not long enough to ensure that the same key isn't used twice. There's also another flaw whereby you can inject special packets to make the AP spew IVs but that's another story :p
theymos
Administrator
Legendary
*
Offline Offline

Activity: 5390
Merit: 13425


View Profile
March 30, 2011, 06:39:07 AM
 #16

If you manage to gather lots of IVs then you can use ones that are the same/similar to do a statistical attack to discover the secret key.

That sort of key-recovery attack is specific to RC4 (and maybe other stream ciphers), I believe.

1NXYoJ5xU91Jp83XfVMHwwTUyZFK64BoAD
Steve
Hero Member
*****
Offline Offline

Activity: 868
Merit: 1008



View Profile WWW
March 31, 2011, 05:20:42 PM
 #17

This is a great step forward.  I would say that the solution needs to stay focused on the individual user wanting to easily hold some bitcoins and use it for transactions.  Thus, I would vote against things like allowing for multiple keys to be used within the same wallet for different subsets of keys.  Merchants or service providers needing such advanced features can customize the basic client code.

Also, assuming it doesn't weaken the security, I don't think encrypted the entire wallet is necessary (just the private bitcoin keys in the DB is fine).

I would also suggest that you not use the users passphrase directly, but instead use a hash (i..e sha256) of their passphrase for the encryption.  This is not to add any additional security to the wallet itself, but to prevent a user's passphrase from being exposed should the bitcoin client be compromised and the decryption key is retrieved (and thereby offering some protection for users that use the same passphrase for many things).  You also need to ensure the user's unhashed passphrase doesn't stick around anywhere in bitcoins' memory by accident.

From a UI perspective, I think on startup, and when accessing the wallet...if it is an encrypted wallet, the user should be prompted for their password...there should be an option the user can set on how long the client will retain the hashed password before overwriting and discarding it from memory...after such time, if the user needs to do something involving a private bitcoin key, they will once again be prompted for a password.  If this is set to 5 minutes or so by default, it's a fair amount of time to have the wallet lock itself in case a user leaves the client unattended.  Another option could also allow the user to require a password on ever use of a private key and for the client to never retain the hashed password beyond the time it takes to decrypt and use the private key (also, decrypted keys should never be retained in memory for longer than the time necessary to use them).

Note, it might also be a good idea to lock the entire client and mask the UI when the passphrase timeout expires (this is effectively re-using the wallet encryption feature to lock the entire client, but I wouldn't want to have to use a separate passphrase for this purpose).


(gasteve on IRC) Does your website accept cash? https://bitpay.com
Hal
VIP
Sr. Member
*
expert
Offline Offline

Activity: 314
Merit: 4276



View Profile
March 31, 2011, 10:37:19 PM
 #18

Steve, the way the client works now, all the wallet keys are read into memory at startup. Subsequent changes are made in memory and written to disk. This proposed patch decrypts keys as they are read into memory at startup, and encrypts new keys as they are written out to disk.

Decrypting keys on use wouldn't be too hard. Add a popup to read the passphrase, perhaps cache it for a while as you suggest.

Strangely, the hard part is encrypting new keys, because Bitcoin creates keys at odd times. The oddest time is when receiving a payment to the "current address" displayed at the top of the window. Bitcoin likes to keep things fresh so it generates a new address to display, and new key. But it can't encrypt the new key without the passphrase, and in general no user is around to respond to a popup at random times.

Hal Finney
Steve
Hero Member
*****
Offline Offline

Activity: 868
Merit: 1008



View Profile WWW
April 01, 2011, 10:10:06 PM
 #19

Strangely, the hard part is encrypting new keys, because Bitcoin creates keys at odd times. The oddest time is when receiving a payment to the "current address" displayed at the top of the window. Bitcoin likes to keep things fresh so it generates a new address to display, and new key. But it can't encrypt the new key without the passphrase, and in general no user is around to respond to a popup at random times.

I think the solution is to fix the client...this is an odd behavior.  It's not really necessary to have an address displayed at all times.  When receiving payment, the client could just clear the current address from the display.  If no user is around, then it's impossible for them to share that address anyway...so no point in generating a new address until a user actually wants to send it to someone for use in a payment.  The copy to clipboard button could trigger the generation of a new address when there isn't a current one.

(gasteve on IRC) Does your website accept cash? https://bitpay.com
Pieter Wuille
Legendary
*
qt
Offline Offline

Activity: 1072
Merit: 1181


View Profile WWW
April 14, 2011, 12:29:28 AM
Last edit: April 14, 2011, 01:18:51 AM by sipa
 #20

Would it be cryptographically safe to use the (hash of) the public key as IV when encrypting the private key part?

Another idea: store the actual randomly-generated AES key for encryption of the private keys in the wallet itself, encrypted itself using a key derived (using the nice key derivation function you have for scratch-cards?) from the passphrase. This way, you can easily change the passphrase without needing to decrypt/reencrypt everything in the wallet. This encrypted key could also contain a hashed version of itself, to verify integrity.

A change passphrase RPC call / wx thing could be added, and setting it to the empty string would remove encryption (and turn all ekey fields back to key fields).

I do Bitcoin stuff.
Pages: [1]
  Print  
 
Jump to:  

Powered by MySQL Powered by PHP Powered by SMF 1.1.19 | SMF © 2006-2009, Simple Machines Valid XHTML 1.0! Valid CSS!