How to implement Diffie-Hellman in JavaScript?

5

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

What is the best way to do this in JavaScript? Is there anything ready in the browser , or maybe some library ready to do that? Or I'll have to implement it by hand (in this case, using some arbitrary precision integer manipulation library).

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

    
asked by anonymous 09.04.2015 / 05:02

1 answer

5

A WebCryptoAPI - in the process of being standardized by the W3C and already supported at least in part by most modern browsers - has methods for generating DH keys . It is not necessary or advisable to do this by hand, since the possibility of error is enormous (and most likely the performance will be worse if done in pure JavaScript instead of using the functions provided by the browser - most likely optimized in native code).

But before ...

... a warning: using Diffie-Hellman by itself does not guarantee end-to-end ownership, it does not make your communication automatically secure against MitM , and anyway when done in JavaScript most of the benefits of encryption will nullify. I explain:

  • First, you need to establish a basic premise: do users trust on your server or not? When encrypted, this is done for some reason, usually in the case of someone unreliable having access to the data being exchanged. Securing the communications channel is easy: just use SSL / TLS. The problem is just the data at rest, usually accessible by the server and the people who have access to it (system administrators, for example).

    If this server is not considered "trusted" then we have a problem: how is it that sends the JavaScript pro client, how to ensure that this JavaScript is not malicious? How to ensure that it does not contain a backdoor , or have some other purposeful failure that overrides the cryptographic guarantees. Personally, I do not find this "useless" as many people preach, but it's good to keep that in mind when designing a system with this architecture ...

  •   

    Diffie-Hellman is a key exchange algorithm . The sure question to ask is: exchange a key, yes, but with whom?

         

    - Tom Leek, no security.SE

    If Alice and Bob want to communicate safely, and Mallory interferes with this communication, we say that there was a Man-in-the-Middle attack (in the case wo man 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 the communication being the wrong person!

    Do not just Alice do DH with someone who says Bob , she needs to do DH with someone she knows to be Bob. Otherwise, Alice may end up doing DH with Mallory, and Mallory with Bob, so Alice thinks that she is communicating directly with Bob (and vice versa) but in reality this communication is taking place (which can read and also change the messages exchanged).

    If Alice has some means of identifying Bob (a public key, or perhaps access to a common CA ), and vice versa, so it is enough that Alice sign the values to be sent to Bob, and verify the signature of the values received from Bob. So both will know that they are doing DH with the right person.

  • An example

    Given these alerts, a complete example of using DH via WebCrypto follows. Note that this example may not work on 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;
    }
        
    09.04.2015 / 05:02