javascriptjavaencryptionrsajsencrypt

Error 'javax.crypto.BadPaddingException: Decryption error' while trying decrypt data encrypted in the client-side with JSEncrypt


I am trying decrypt in a web application (developed with java/servlet) a token encrypted on the client-side with a javascript/JSEncrypt code.

this is the javascript code:

function submit() {
    var form = document.querySelector("form");
    var plainText = document.querySelector("input[name=ip]").value;

    var fileInput = document.createElement('input');
    fileInput.type = 'file';

    fileInput.addEventListener('change', function (e) {
        var file = e.target.files[0];
        var reader = new FileReader();

        reader.onloadend = function (e) {
            var privateKey = e.target.result;

            var encryptor = new JSEncrypt();
            encryptor.setPrivateKey(privateKey);
            var encryptedText = encryptor.encrypt(plainText);

            const params = new URLSearchParams();
            params.append('token', encryptedText);

            fetch(form.action, {
                method: form.method,
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded'
                },
                body: params
            })
            .then(response => {
                var main = document.querySelector("main");
                var p = document.createElement("h1");
                p.textContent = response.text();
                main.appendChild(p);
                //window.location.href = "http://localhost/inbox/home";
            })
            .catch(error => {
                var main = document.querySelector("main");
                var p = document.createElement("h1");
                p.textContent = error.text();
                main.appendChild(p);
            });            
        };

        reader.readAsText(file);
    });

    fileInput.click();
}

this is the java code that is trying to do the decryption:

public class RSADecryptor {
    public static String decrypt(byte[] cipherText, PublicKey publicKey) throws Exception {
        Cipher cipher = Cipher.getInstance(RSAKeyGenerator.RSA_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, publicKey);

        int keySize = ((RSAKey) publicKey).getModulus().bitLength();
        int blockSize = keySize / 8;

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        int offset = 0;

        while(offset < cipherText.length) {
            int length = Math.min(cipherText.length - offset, blockSize);
            byte[] block = cipher.doFinal(cipherText, offset, length);
            outputStream.write(block, 0, block.length);
            offset += blockSize;
        }

        byte[] decryptedBytes = outputStream.toByteArray();

        //byte[] decryptedBytes = cipher.doFinal(cipherext);
        return new String(decryptedBytes);
    }
}

Anyone can point out what I am doing wrong here?


Solution

  • Both libraries have dedicated functions for signing and verification. For JSEncrypt, see the documentation, sec. How to use this library, 2nd code snippet, for Java, see the Signature class.

    In the case of JSEncrypt, a possible implementation for signing is (using RSASSA-PKCS1-v1_5 as signature algorithm and SHA256 as digest):

    var privPkcs8Pem = `-----BEGIN PRIVATE KEY-----
    MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCYiWw16W1eGdG2
    INU24v4EBjir6dYRQ4rhacVhiqOGXvnvC3M7XYMnT1+rTVTJpPrl/KY7OnskOYJj
    BQOdV61hh7/uacvfG27xKuBmm5YZOcIcWHWzuMijF9MynmnzeEBVGbUlm5NyMaS4
    xNVNnv/c3jagWHXDnygzQgvW6K7HmZ6+ZWCxKa+dpcVtnrG7dApdDbMkMaj+zJvL
    1fWDrQYDPWrt3bdewXBHA6LjwyrQfuOvVxlxSuvvo1E/Xi0aW5taZQQi86ltz331
    uLGp7pZnhIQPldDhoXSX4TI5rv12LEs9KPKLDiG1e4QkjeDCJAG4uHxEvcGA7LXz
    GSAs2y/DAgMBAAECggEAFhYBGjXqv9nK221El0u6+OKHuR3vVhTqr88MpR4ZpZ6X
    NLf6iX7K5dm1zKFdT0xj3FdpIEv27DH8LDyGdJ/vJZ8uDftj6VwuWPKwMYXXRV7E
    uYL2GT1+UN27FuoIsEKEmhh8pJiH+wcExpC6y8rjrttpRcKZMT0YaSznzEho6ihy
    6HtHMkXOI6KO/aZDRbI9n6Sdkul4qWmBAHj1CcKAFAHOaxVzYHpMaIPcZ+Mr++Bw
    +m0qyMOlYvXFnUvpgwgL5KQ5id6wLT6WrBOybaHfmtAsVV7/baAxh7urdEHv+zCc
    YJHhM/PnFOTzTfkUMDt1pWJyMAZCAygZWesaHtzsiQKBgQDS2RRivF7TekB/Tl+C
    nFWF4SR9YNeIKXMywFiyQDD2lPnMwagWjcbCqp9ZjYhv3siAHhsTCbnkSsnQcq40
    CxqUxeDmX6PZCTVgWw0Uj6IZmPUVDj9twVWYyv5FF33C5Qwwwdu7DSZaA8Jl+JKv
    InMsI6vNdCN6Of94F3pnBu1NawKBgQC5M6kMX+negUUJQPeWEjt4TCvUxqWMUmkx
    4ly8XyCOCM6Wt1kmhHN1NYs4+O60cO8x/6JSBq94eiS5otpDBGTL1tRjq3Kvvytg
    jOpAlNIhfEBiPABfcwV/+k2fz8uGFZFMIUliDMLAa4wxo3rBbmuSPEsioyQ3dOmV
    /GnHELklCQKBgHFOzUC+QCtfsFd5s6QKBX+73RMvvsPimpC0gzXPf0CUEKXzkDQG
    nsCwVpAWmjKcQ51uEFirymUft9K4Plujd/ZpXJIQ5YlWBIQyihX5lkAxTcux6249
    DpXcyMYyeJgK5QEyvLWJvIl1KbwI2DMbzU70IHh5qDMgBeTwoQvK0i3fAoGBAKQX
    nLmlJd8KpHNth7EFGIIe41sUYsvwnNohGU+h7YNLVFf/vdK92lrIhUGGdmGUCqs/
    N7/7wm85sd5053QnqXNeNjLVTrle5X0XfdqYwZH/uEARr7bif8YDrdFiWI7F/0X3
    3EAu1EOPRtkYYwSN5GveVigrakRkpy5IRiSlsZWZAoGAM3y4tYRbOVCmp4Z419gQ
    /9HlEgRjBaGnDQRcDEFqcLtIm1C6mygdjP+wCkDNwVoszSbsgdRMuoZ2ReD6gFRv
    bBZM7lHICA5tPaARgGgeKomUZnozTxgVH7FMxax+BJPrI/dpXcX0fCNinAJo48l0
    GOHy1iLgxKUEEli3j6hchjU=
    -----END PRIVATE KEY-----`
    
    var sign = new JSEncrypt()
    
    // key import
    sign.setPrivateKey(privPkcs8Pem)
    
    // signing
    var message = 'The quick brown fox jumps over the lazy dog'
    var signature = sign.sign(message, CryptoJS.SHA256, 'sha256')
    
    console.log(signature) // E888/KGgC0Zgw8/1Ojd5wxMI/0KmuzhP+YYoBawSb7w6EGRvPf0SXSYfZeiiiEDMFKchb9k6lH4M/Foi++y2Lvuaqd8ZA+/fv1uiAhzwAzBsNolPWIxN3K1yO9kc+d0YZCDaEz7abasDOubppd6cNnyIgZnEwd3JjSoqs+zWayKxYGb1CqbhXVNKWoRgwkPqzs1pw0kcZsAuTBa0uZ/hAYIdaQh7X7n/ZjNXCddZ2iHQJExtPNquay80pfL1HVA0FlX1QM1smw3OP/hj7DSYswhayr98pKIPZeLtrbzOBrpLfk/nF/YBIEj+1KQH5ZD9wjK7q4ulaF6uy03rAZ5eaQ==
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jsencrypt/3.3.2/jsencrypt.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>

    A possible Java counterpart for verification is:

    import java.nio.charset.StandardCharsets;
    import java.security.KeyFactory;
    import java.security.PublicKey;
    import java.security.Signature;
    import java.security.interfaces.RSAPublicKey;
    import java.security.spec.X509EncodedKeySpec;
    import java.util.Base64;
    
    ...
    
    String pubX509Pem = """
    -----BEGIN PUBLIC KEY-----
    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmIlsNeltXhnRtiDVNuL+
    BAY4q+nWEUOK4WnFYYqjhl757wtzO12DJ09fq01UyaT65fymOzp7JDmCYwUDnVet
    YYe/7mnL3xtu8SrgZpuWGTnCHFh1s7jIoxfTMp5p83hAVRm1JZuTcjGkuMTVTZ7/
    3N42oFh1w58oM0IL1uiux5mevmVgsSmvnaXFbZ6xu3QKXQ2zJDGo/syby9X1g60G
    Az1q7d23XsFwRwOi48Mq0H7jr1cZcUrr76NRP14tGlubWmUEIvOpbc999bixqe6W
    Z4SED5XQ4aF0l+EyOa79dixLPSjyiw4htXuEJI3gwiQBuLh8RL3BgOy18xkgLNsv
    wwIDAQAB
    -----END PUBLIC KEY-----""";
            
    // key import
    String pubX509DerB64 = pubX509Pem.replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "").replace("\n", "");
    byte[] pubX509Der = Base64.getDecoder().decode(pubX509DerB64);      
    KeyFactory kf = KeyFactory.getInstance("RSA");
    X509EncodedKeySpec keySpec = new X509EncodedKeySpec(pubX509Der);
    PublicKey pubKey = (RSAPublicKey) kf.generatePublic(keySpec);
    
    // verification
    byte[] message = "The quick brown fox jumps over the lazy dog".getBytes(StandardCharsets.UTF_8);
    byte[] signature = Base64.getDecoder().decode("E888/KGgC0Zgw8/1Ojd5wxMI/0KmuzhP+YYoBawSb7w6EGRvPf0SXSYfZeiiiEDMFKchb9k6lH4M/Foi++y2Lvuaqd8ZA+/fv1uiAhzwAzBsNolPWIxN3K1yO9kc+d0YZCDaEz7abasDOubppd6cNnyIgZnEwd3JjSoqs+zWayKxYGb1CqbhXVNKWoRgwkPqzs1pw0kcZsAuTBa0uZ/hAYIdaQh7X7n/ZjNXCddZ2iHQJExtPNquay80pfL1HVA0FlX1QM1smw3OP/hj7DSYswhayr98pKIPZeLtrbzOBrpLfk/nF/YBIEj+1KQH5ZD9wjK7q4ulaF6uy03rAZ5eaQ==");
    Signature verifier = Signature.getInstance("SHA256withRSA");
    verifier.initVerify(pubKey);
    verifier.update(message);
    boolean result = verifier.verify(signature);
    
    System.out.println(result); // true