keybase / kbpgp

OpenPGP (RFC4880) Implementation in IcedCoffeeScript
https://keybase.io/kbpgp
BSD 3-Clause "New" or "Revised" License
535 stars 74 forks source link

How do I login to keybase, retrieve my private key, and then use it to decrypt a message? #105

Closed lettergram closed 8 years ago

lettergram commented 8 years ago

I recognize this is not directly related to kbpgp. However, without receiving the proper private key it is impossible to decode messages that were encrypted from keybase. Between keybase and kbpgp I have bee unable to find a solution to this problem.

The current code I am currently using is below:

/**                                                                                            
 *  Connect to get user keys                                                                   
 */
connect = function() {

// AJAX login to get private key:                                                          
// Step One: Salt  
$.ajax({
        async: true,
        type: 'GET',
        url: "https://keybase.io/_/api/1.0/getsalt.json",
        async: false,
        data: {"email_or_username": username},

        success: function(salt) {
            if(salt && salt.status && salt.status.name == "OK"){

                var scrypt = scrypt_module_factory(67108864);
                var pwh = scrypt.crypto_scrypt(scrypt.encode_utf8(user_passphrase),
                                               scrypt.encode_utf8(salt.salt),
                                               Math.pow(2,15), 8, 1, 224).slice(192, 224);

                var login_session = CryptoJS.enc.Base64.parse(salt.login_session);
                var parsed_pwh = CryptoJS.enc.Utf8.parse(pwh);
                var hmac_pwh = CryptoJS.HmacSHA512(login_session, parsed_pwh);
                hmac_pwh = CryptoJS.enc.Hex.stringify(hmac_pwh);

                var login_data = {
                    email_or_username: username,
                    csrf_token: salt.csrf_token,
                    hmac_pwh: hmac_pwh,
                    login_session: salt.login_session
                };

                // AJAX login to get private key:                                              
                // Step Two: Login                                                             
                $.ajax({
                    async: false,
                    type: 'POST',
                    url: "https://keybase.io/_/api/1.0/login.json",
                    data: login_data,
                    dataType: "json",
                    success: function(login) {

                        if(login && login.status && login.status.name == "OK"){
                            console.log("successful login");
                        }else{
                            console.log("failed login: " + login.status.name);
                        }
                    },
                     error: function (request, status, err) {
                         console.log(err + status);
                     } 
                });
            }else{
                console.log("failed salt");
            }
        },
         error: function (request, status, err) {
             console.log(err + status);
         }
     });

I receive an error that my login_password was incorrect. Any help anyone could give would be great.

lettergram commented 8 years ago

Apparently, I made a duplicate #106

maxtaco commented 8 years ago

In this function:

function hex2bin(hex) {
    var bytes = [], str;
    for(var i=0; i< hex.length-1; i+=2)
        bytes.push(parseInt(hex.substr(i, 2), 16));
    return String.fromCharCode.apply(String, bytes);
}

you want to populate a Uint8Array, and not a string.

lettergram commented 8 years ago

That should be exactly what scrypt.encode_utf8(salt.salt) does already:

console.log(salt.salt)

edf99bbe4a245bf6a81603ba5e5d0cd4

console.log(scrypt.encode_utf8(salt.salt)): 

Uint8Array[32] 0: 101 1: 100 2: 102 ....

I then rewrote hex2bin:

function hex2bin(hex) {
    var bytes = [], Uint8Array;
    for(var i=0; i< hex.length-1; i+=2)
        bytes.push(parseInt(hex.substr(i, 2), 16));
    return bytes;
}

and the output is indeed different:

[208, 245, 81, 102, 126, 226, 123, 27, 145, 18, 95, 140, 3, 222, 220, 11, 40, 210, 60, 123, 195, 16, 35, 0, 87, 86, 73, 73, 222, 11, 151, 211]

However, I still receive the same error: BAD_LOGIN_PASSWORD

Updated the code looks like this:

    success: function(salt) {
        if(salt && salt.status && salt.status.name == "OK"){

            var scrypt = scrypt_module_factory(67108864);
            var pwh = scrypt.crypto_scrypt(scrypt.encode_utf8(user_passphrase),
                                           hex2bin(salt.salt),
                                           Math.pow(2,15), 8, 1, 224).slice(192, 224);

            var login_session = CryptoJS.enc.Base64.parse(salt.login_session);
            var parsed_pwh = CryptoJS.enc.Utf8.parse(pwh);
            var hmac_pwh = CryptoJS.HmacSHA512(login_session, parsed_pwh);
            hmac_pwh = CryptoJS.enc.Hex.stringify(hmac_pwh);

            var login_data = {
                email_or_username: username,
                csrf_token: salt.csrf_token,
                hmac_pwh: hmac_pwh,
                login_session: salt.login_session
            };

I still do not know if it a problem with the salt or not either. I have spent many days/weeks trying to figure this out to no avail....

maxtaco commented 8 years ago

No, the decoding of 'edf99bbe4a245bf6a81603ba5e5d0cd4' should be:

0 : 247
1 : 249
2 : 155

Can you check that's what you get? IT should be a 16 byte Uint8Array, and not a 32 byte Uint8Array

maxtaco commented 8 years ago

Send pwh PGP-encrypted to max@keybase.io, I can tell you if it's right or not.

lettergram commented 8 years ago

Sorry, hex2bin appears to be:

[237, 249, 155, 190, 74, 36, 91, 246, 168, 22, 3, 186, 94, 93, 12, 212]

It is indeed a 16 byte Uint8Array.

I sent you an email, thank you for looking into it

lettergram commented 8 years ago

Thank you @maxtaco I found a solution:

// AJAX login to get private key:
// Step One: Salt
$.ajax({
async: true,
type: 'GET',
url: "https://keybase.io/_/api/1.0/getsalt.json",
async: false,
data: {"email_or_username": username},

success: function(salt) {
    if(salt && salt.status && salt.status.name == "OK"){

    var scrypt = scrypt_module_factory(67108864);
    var pwh = scrypt.crypto_scrypt(scrypt.encode_utf8(user_passphrase),
                                   hex2bin(salt.salt),
                                   Math.pow(2,15), 8, 1, 224).slice(192, 224);

    var login_session = CryptoJS.enc.Base64.parse(salt.login_session);
    var parsed_pwh = CryptoJS.enc.u8array.parse(pwh);
    var hmac_pwh = CryptoJS.HmacSHA512(login_session, parsed_pwh);
    hmac_pwh = CryptoJS.enc.Hex.stringify(hmac_pwh);

    var login_data = {
        email_or_username: username,
        csrf_token: salt.csrf_token,
        hmac_pwh: hmac_pwh,
        login_session: salt.login_session
    };

    // AJAX login to get private key:
    // Step Two: Login
    $.ajax({
        async: false,
        type: 'POST',
        url: "https://keybase.io/_/api/1.0/login.json",
        data: login_data,
        dataType: "json",
        success: function(login) {
        console.log(login);
        if(login && login.status && login.status.name == "OK"){
            console.log("successful login");
            console.log(login.me);
        }else{
            console.log("failed login: " + login.status.name);
        }
        },
        error: function (request, status, err) {
        console.log(err + status);
        }
    });

    }else{
    console.log("failed salt");
    }
},
error: function (request, status, err) {
    console.log(err + status);
}
});
maxtaco commented 8 years ago

For those who google to here: the bug was here: var parsed_pwh = CryptoJS.enc.Utf8.parse(pwh);, which caused some characters in your passphrase hash to be dropped (0x1b in particular).

maxtaco commented 8 years ago

:+1: