javascriptencryptionrsawebcrypto

WebCrypto execution problem with failure due to data type error and compiling all processed results in a unified field


I am trying to use a static RSA Public Key to encrypt a session generated AES key which is then used to encrypt a password and the randomly generated AES session key fed into the RSA encryption cipher had the following error message:

TypeError: Failed to execute 'encrypt' on 'SubtleCrypto': The provided value is not of type '(ArrayBuffer or ArrayBufferView)'

I tried to convert the randomly generated AES key data (via exporting) into a Uint8Array buffer before converting it to an ArrayBuffer to be used in the RSA encryption code but somehow it is not working.

After managing to convert the Array Buffer field with the help of @pedrofb,I tried to pull all results into a unified field for central storage of the processed outputs which gave an inconsistent output.

Here is the modified source code that fixes the failed execution but yields another problem of not having consistent output values in the HTML 'input' field. The HTML input field is only showing partial data collected across all the operations. I am trying to use a 'hidden' field as a sort of collector to collect all processed results but when I transfer the processed results to a non-hidden field, it only appears partial.

<html>
    <body>
        <script>
            var secBuff = null;         
            function encryptPassword(rsaPublicKeyModulusHex, password) {
                var enc = new TextEncoder();
                var iv = window.crypto.getRandomValues(new Uint8Array(16));
                document.getElementById("a").value = buff2hex(iv);
                var b64UrlPEM = h2b64("30820122300d06092a864886f70d01010105000382010f003082010a0282010100" + rsaPublicKeyModulusHex + "0203010001", true);
                var binaryDerStr = window.atob(b64UrlPEM);
                var binDERData = str2ab(binaryDerStr);

                // Generate session AES key and encrypt password/pin
                window.crypto.subtle.generateKey(
                    {
                        name: "AES-CBC",
                        length: 256,
                    },
                    true,
                    ["encrypt", "decrypt"]
                )
                .then(function(key){

                    // Export AES Session Key bytes
                    window.crypto.subtle.exportKey("raw", key)
                    .then(function(data){
                        // Convert AES Session secret key to buffer object
                        secBuff = typedArrayToBuffer(new Uint8Array(data));

                        // Import RSA Public Modulus
                        window.crypto.subtle.importKey(
                            "spki",
                            binDERData,
                            {
                                name: "RSA-OAEP",
                                hash: "SHA-256"
                            },
                            false,
                            ["encrypt"]
                        )
                        .then(function(publicKey){

                            // Encrypt AES session key with public key
                            window.crypto.subtle.encrypt(
                                {
                                    name: "RSA-OAEP"
                                },
                                publicKey, // from above imported RSA Public Key
                                secBuff    // ArrayBuffer of session secret key
                            )
                            .then(function(encrypted){
                                var pre = document.getElementById("a").value;
                                document.getElementById("a").value = pre + buff2hex(new Uint8Array(encrypted));
                            })
                            .catch(function(err){
                                console.error(err);
                            });

                        })
                        .catch(function(err){
                            console.error(err);
                        });
                    })
                    .catch(function(err){
                        console.error(err);
                    });

                    // Encrypt plaintext from somewhere
                    window.crypto.subtle.encrypt(
                        {
                            name: "AES-CBC",
                            iv
                        },
                        key,
                        enc.encode(password)
                    )
                    .then(function(encrypted){
                        var pre = document.getElementById("a").value;
                        document.getElementById("a").value = pre + buff2hex(new Uint8Array(encrypted));
                    })
                    .catch(function(err){
                        console.error(err);
                    });
                })
                .catch(function(err){
                    console.error(err);
                });
            }

            function h2b64(hex, url) {
                var output = btoa(hex.match(/\w{2}/g).map(function(a) {
                    return String.fromCharCode(parseInt(a, 16));
                }).join(""));
                if (url) {
                    output = output.replace(/=/g,"");
                }
                return output;
            }

            function str2ab(str) {
                const buf = new ArrayBuffer(str.length);
                const bufView = new Uint8Array(buf);
                for (let i = 0, strLen = str.length; i < strLen; i++) {
                    bufView[i] = str.charCodeAt(i);
                }
                return buf;
            }

            function typedArrayToBuffer(array) {        
                return array.buffer.slice(array.byteOffset, array.byteLength + array.byteOffset);
            }

            function buff2hex(buffer) {
                return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join('');
            }
        </script>
        <input type="hidden" id="a">
        </input>
        <textarea rows="4" cols="50" id="b">
        </textarea>
        <script>
            var blob = encryptPassword('b1cea3fd81bdd475315fda596dee95bd4b54d2954b936fee143689cc7936d5400eecf201b18f016ce307b43d4eeb6e7794c74731d907a37517e055850984db5d3f065902e190e469f004d1ed0ceb7696db98026053aa0323b24657c8351bd09510c54b174fb11c0f8ea0c59bdda93597a2176281b7b12c43fd29330cba45ce9ce533ef62450d21222938e4dfdf08a18a38ebb58ea9809a7ba4f129e3df8f2adb65870c906b8a5d89d4d6a8bf4e54c604c0f43ad92403ce1fa1ac7cac1070dc28072cb90d19ee48c0664225bf9fb24af47924a7b3a15f183a3c330999ed3d9d318efcbbd2fd586a1edd232f9f4cef01db79ae1f00bfc705bf5e3e621ad081a3a5', 'abc');
            document.getElementById("b").value = document.getElementById("a").value;
        </script>
    </body>
</html>

Here is the original source code where the problem occurred for archival:

<html>
    <body onload="encryptPassword('b1cea3fd81bdd475315fda596dee95bd4b54d2954b936fee143689cc7936d5400eecf201b18f016ce307b43d4eeb6e7794c74731d907a37517e055850984db5d3f065902e190e469f004d1ed0ceb7696db98026053aa0323b24657c8351bd09510c54b174fb11c0f8ea0c59bdda93597a2176281b7b12c43fd29330cba45ce9ce533ef62450d21222938e4dfdf08a18a38ebb58ea9809a7ba4f129e3df8f2adb65870c906b8a5d89d4d6a8bf4e54c604c0f43ad92403ce1fa1ac7cac1070dc28072cb90d19ee48c0664225bf9fb24af47924a7b3a15f183a3c330999ed3d9d318efcbbd2fd586a1edd232f9f4cef01db79ae1f00bfc705bf5e3e621ad081a3a5', 'abc')">
        <script>
            var secBuff = null;
            function encryptPassword(rsaPublicKeyModulusHex, password) {                
                var enc = new TextEncoder();
                var iv = window.crypto.getRandomValues(new Uint8Array(16));
                var b64UrlPEM = h2b64("30820122300d06092a864886f70d01010105000382010f003082010a0282010100" + rsaPublicKeyModulusHex + "0203010001", true);
                var binaryDerStr = window.atob(b64UrlPEM);
                var binDERData = str2ab(binaryDerStr);

                // Generate session AES key and encrypt password/pin
                window.crypto.subtle.generateKey(
                    {
                        name: "AES-CBC",
                        length: 256,
                    },
                    true,
                    ["encrypt", "decrypt"]
                )
                .then(function(key){

                    // Export AES Session Key bytes
                    window.crypto.subtle.exportKey("raw", key)
                    .then(function(data){
                        // Convert AES Session secret key to buffer object
                        secBuff = typedArrayToBuffer(new Uint8Array(data));
                    })
                    .catch(function(err){
                        console.error(err);
                    });

                    // Encrypt plaintext from somewhere
                    window.crypto.subtle.encrypt(
                        {
                            name: "AES-CBC",
                            iv
                        },
                        key,
                        enc.encode(password)
                    )
                    .then(function(encrypted){
                        console.log("IV: " + iv);
                        console.log("Ciphertext: " + new Uint8Array(encrypted));
                    })
                    .catch(function(err){
                        console.error(err);
                    });
                })
                .catch(function(err){
                    console.error(err);
                });

                // Import RSA Public Modulus
                window.crypto.subtle.importKey(
                    "spki",
                    binDERData,
                    {
                        name: "RSA-OAEP",
                        hash: "SHA-256"
                    },
                    false,
                    ["encrypt"]
                )
                .then(function(publicKey){

                    // Encrypt AES session key with public key
                    window.crypto.subtle.encrypt(
                        {
                            name: "RSA-OAEP"
                        },
                        publicKey, // from above imported RSA Public Key
                        secBuff    // ArrayBuffer of session secret key
                    )
                    .then(function(encrypted){
                        console.log(new Uint8Array(encrypted));
                    })
                    .catch(function(err){
                        console.error(err);
                    });

                })
                .catch(function(err){
                    console.error(err);
                });             
            }

            function h2b64(hex, url) {
                var output = btoa(hex.match(/\w{2}/g).map(function(a) {
                    return String.fromCharCode(parseInt(a, 16));
                }).join(""));
                if (url) {
                    output = output.replace(/=/g,"");
                }
                return output;
            }

            function str2ab(str) {
                const buf = new ArrayBuffer(str.length);
                const bufView = new Uint8Array(buf);
                for (let i = 0, strLen = str.length; i < strLen; i++) {
                    bufView[i] = str.charCodeAt(i);
                }
                return buf;
            }

            function typedArrayToBuffer(array) {                
                if (array.constructor === Uint8Array) {
                    console.log("Uint8Array input: " + array);
                    console.log(array.byteLength);
                } else {
                    console.log("Uint8Array input NOT detected ...");
                }               
                return array.buffer.slice(array.byteOffset, array.byteLength + array.byteOffset);
            }
        </script>
    </body>
</html>

Solution

  • The second part of your code (Import RSA Public Modulus) is running at the same time as the first one (because webcrypto promises are asynchronous )so the secBuff variable is not initialized.

    Make sure you invoke the section that begins with // Import RSA Public Modulus after secBuff = typedArrayToBuffer (new Uint8Array (data));

    // Export AES Session Key bytes
    window.crypto.subtle.exportKey("raw", key)
        .then(function(data){
             // Convert AES Session secret key to buffer object
             secBuff = typedArrayToBuffer(new Uint8Array(data));
             //Call Import RSA Public Modulus here
         })