How to implement Diffie-Hellman in Javascript?

Asked

Viewed 699 times

5

I am developing a web system where users can communicate with each other privately, without even the server having access to the content of the communication (i.e. end-to-end). For this I intend to encrypt all messages exchanged between them on the browser, using a key created through the Diffie-Hellman protocol.

What is the best way to do this in Javascript? There is something ready in itself browser, or maybe some ready-made library that does this? Or I will have to implement by hand (in case, using some arbitrary precision integer manipulation library).

Finally: this is enough to protect communication against attacks Man-in-the-Middle?

1 answer

5

To Webcryptoapi - in the process of standardization by W3C and already supported at least in part by most of the browsers modern - possesses methods for DH key generation. It is not necessary, nor recommendable, to do this by hand, because the possibility of error is huge (and very likely the performance will be worse, if done in pure Javascript instead of using the functions provided by the own browser - most likely optimized in native code).

But first...

...an alert: the use of Diffie-Hellman by itself does not guarantee ownership end-to-end, does not make your communication automatically secure against Mitm, and anyway when done in Javascript most of the benefits of encryption are nullified. Explain:

  1. First, we need to establish a basic premise: users trust on your server or not? When encrypting, this is done for some reason, usually in case someone unreliable has access to the data being exchanged. Protect the communications channel It’s easy: just use SSL/TLS. The problem is only data at rest, generally accessible by the server and the people who have access to it (system administrators, for example).

    If this server is not considered "reliable", then we have a problem: how does it send Javascript to the client, how do we ensure that this Javascript is not malicious? How to ensure that it does not contain a backdoor, or has some other purposeful flaw that nullifies the guarantees of cryptography. Personally, I don’t find this "useless" as many people preach, but it’s good to keep this consideration in mind when designing a system with this architecture...

  2. The Diffie-Hellman is a key exchange algorithm. The question certain to be done is: exchange a key, yes, but with whom?

    -- Tom Leek, security.SE

    If Alice and Bob want to communicate safely, and Mallory interferes with that communication, we say there’s been an attack "Man-in-the-Middle" (in the case Woman in the Middle, but I’m rambling...), and this is something that DH proposes to avoid. The problem is that a DH key exchange can be done perfectly, but the person at the other end of communication be the wrong person!

    It’s not enough Alice does DH with someone who calls himself Bob, she needs to do DH with someone she know be Bob. Otherwise, Alice may end up doing DH with Mallory, and Mallory with Bob, so that Alice think that is communicating directly with Bob (and vice versa) but in reality this communication is happening through Mallory (who can read and also change the messages exchanged).

    In case Alice has some way to identify Bob (a public key, or maybe access to a common CA), and vice versa, so it’s enough that Alice signing the amounts to be sent to Bob, and check the signature of the values received from Bob. So you will both know that you are doing DH with the right person.

An example

Given these alerts, follow a full example of using DH via Webcrypto. Note that this example may not work at all browsers (in Chrome, for example, would only work if the page was served via HTTPS):

var parametros = {
    name: "DH",
    // NOTA: ESSE É UM PRIMO PEQUENO PARA TESTES SOMENTE! NÃO USE NA PRÁTICA!
    // Ver http://datatracker.ietf.org/doc/rfc3526/ para primos melhores
    prime: new Uint8Array([255,255,255,255,255,255,255,255,201,15,218,162,33,104,194,52,196,198,98,139,            128,220,28,209,41,2,78,8,138,103,204,116,2,11,190,166,59,19,155,34,81,74,8,            121,142,52,4,221,239,149,25,179,205,58,67,27,48,43,10,109,242,95,20,55,79,225,            53,109,109,81,194,69,228,133,181,118,98,94,126,198,244,76,66,233,166,55,237,            107,11,255,92,182,244,6,183,237,238,56,107,251,90,137,159,165,174,159,36,17,            124,75,31,230,73,40,102,81,236,228,91,61,194,0,124,184,161,99,191,5,152,218,            72,54,28,85,211,154,105,22,63,168,253,36,207,95,131,101,93,35,220,163,173,            150,28,98,243,86,32,133,82,187,158,213,41,7,112,150,150,109,103,12,53,78,74,            188,152,4,241,116,108,8,202,35,115,39,255,255,255,255,255,255,255,255]),
    generator: new Uint8Array([2]),
};

// Alice gera sua chave...
window.crypto.subtle.generateKey(parametros, false, ["deriveKey", "deriveBits"])
.then(function(chaves){
    var publica = chaves.publicKey;
    var privada = chaves.privateKey;
    
    // Serializa como string (base64)
    window.crypto.subtle.exportKey("raw", publica).then(function(arraybuffer){
        var publicaStr = btoa(String.fromCharCode.apply(null, new Uint8Array(arraybuffer)));
      
        // ASSINA!!!
        // ...
      
        // E envia para Bob
        // $.ajax(...)
        document.body.innerHTML += "<p>Pública de Alice: <pre>" + publicaStr + "</pre></p>";
        
        // Recebe a chave pública de Bob (gerada do mesmo jeito)
        // $.ajax(...)
        var bobStr = "i26tVhsmO6W8WnVu9xBROZOvFTP8n568eXZQtGR9/Ux+6RPOv4Dpkg2qVDP7gx1itY5vdC2r8KUxTfvHps3B9i6xQrlvc7CC3MY667GYp4HJge7M44dEsUTleH/xJTKITRWB7FGgfxJjQ7/z4yx5+KOD0DaLiIamPYL4XwZD3IDqbKYrngXhHNoexYAjrDskG3W0eZpy1fKJiDes9rs9ttTgSBezx+mUfBHpKUWuXzwdJhFJGnvTxW2hTna7gCER";

        // VERIFICA A ASSINATURA!!!
        // ...
      
        // Deserializa e transforma numa chave
        window.crypto.subtle.importKey("raw", _base64ToArrayBuffer(bobStr),
                                       parametros, false, [])
        .then(function(bob){
          
            // Junta com os parâmetros
            var parametrosMaisChave = Object.create(parametros, {
                public:{ value: bob }
            });
          
            // Duas opções:
            // 1. Deriva alguns bits comuns (mesmos derivados por Bob)
            window.crypto.subtle.deriveBits(parametrosMaisChave, privada, 256)
            .then(function(bits){
                // Os bits comuns! :)
                var bitsStr = btoa(String.fromCharCode.apply(null, new Uint8Array(bits)));
                document.body.innerHTML += "<p>Bits comuns: <pre>" + bitsStr + "</pre></p>";
            })
            .catch(function(err){
                document.body.innerHTML += "<p>Erro ao derivar bits:<pre>" + err + "</pre></p>";
            });
          
            // 2. Deriva uma chave comum (mesma derivada por Bob) para algum algoritmo
            var parametrosChaveFinal = { 
                // o tipo de chave que você quer criar baseado nos bits derivados
                name: "AES-CTR", // pode ser qualquer algoritmo AES ("AES-CTR", "AES-CBC", "AES-CMAC", "AES-GCM", "AES-CFB", "AES-KW", "ECDH", "DH", or "HMAC")
                // Os parâmetros de geração para o tipo de algoritmo escolhido
                length: 256, //pode ser 128, 192 ou 256
            };
            window.crypto.subtle.deriveKey(parametrosMaisChave, privada,
                                           parametrosChaveFinal, false,
                                           ["encrypt", "decrypt"])
            .then(function(key){
                // A chave comum! :)
                document.body.innerHTML += "<p>Combinação de chaves OK</p>";
            })
            .catch(function(err){
                document.body.innerHTML += "<p>Erro ao combinar chaves:<pre>" + err + "</pre></p>";
            });
        })
        .catch(function(err){
            document.body.innerHTML += "<p>Erro ao importar chave:<pre>" + err + "</pre></p>";
        });
    })
    .catch(function(err){
        document.body.innerHTML += "<p>Erro ao exportar chave:<pre>" + err + "</pre></p>";
    });
})
.catch(function(err){
    document.body.innerHTML += "<p>Erro ao criar chave:<pre>" + err + "</pre></p>";
});

// http://stackoverflow.com/a/21797381/520779
function _base64ToArrayBuffer(base64) {
    var binary_string =  window.atob(base64);
    var len = binary_string.length;
    var bytes = new Uint8Array( len );
    for (var i = 0; i < len; i++)        {
        bytes[i] = binary_string.charCodeAt(i);
    }
    return bytes.buffer;
}

  • See, there are flaws: https://jsfiddle.net/8evq62jd/

  • @user3163662 And what would be these flaws? If you are referring to NotSupportedError: Operation is not supported that appears in Firefox (option 2), I had already noticed this, and I do not know if it was error in my code or if Firefox does not even support this operation... Anyway, bit derivation (option 1) worked correctly. As for Chrome, the NotSupportedError: Algorithm: Unrecognized name is indicating that Chromium does not support Diffie-Hellman yet, should give in the near future.

  • were these same, is a "baby" yet, I do not think it is valid to use it now even being right and being more advantageous the use. (ps: about the questions I asked, I got good libraries for my purposes and I will continue the old fashion Thanks again haha!!)

Browser other questions tagged

You are not signed in. Login or sign up in order to post.