1

I receive the error "Ciphertext length must be equal to key size." when trying to send an encoded message from javascript to python. I have encoded the bytesarray to base64 in javascript and decoded the base64 back to a bytes array in Python. Yet, the issue seems to persist.

Key Generation (Python):

RSA_SECRET_KEY = rsa.generate_private_key(
                public_exponent=65537,
                key_size=4096
            )
RSA_PUBLIC_KEY = RSA_SECRET_KEY.public_key().public_bytes(
              encoding=serialization.Encoding.PEM,
              format=serialization.PublicFormat.SubjectPublicKeyInfo
            )

Import Key (Javascript):

async function importRsaKey(pem) {
    // fetch the part of the PEM string between header and footer
    const pemHeader = "-----BEGIN PUBLIC KEY-----";
    const pemFooter = "-----END PUBLIC KEY-----";
    const pemContents = pem.substring(pemHeader.length, pem.length - pemFooter.length - 1);
    // base64 decode the string to get the binary data
    const binaryDerString = window.atob(pemContents);
    // // convert from a binary string to an ArrayBuffer
    const binaryDer = str2ab(binaryDerString);

    var key = await crypto.subtle.importKey(
        "spki",
        binaryDer,
        {
            name: "RSA-OAEP",
            hash: "SHA-256"
        },
        true,
        ["encrypt"]
    );

    return key;
}

Encrypt Data (Javascript):

async function encrypt(key, msg) {
    return await crypto.subtle.encrypt(
        {name:"RSA-OAEP"},
        key,
        str2ab(msg)
    )
}

Transmit Data (Javascript):

const handleSubmit = (e) => {
    e.preventDefault();
    const rsa_key_promise = importRsaKey(public_key);
    rsa_key_promise
        .then((rsa_key) =>
            {
                encrypt(rsa_key, "test")
                    .then((data) =>
                    {
                        axiosInstance
                        .post(`auth/token/`, {
                            hash: window.btoa(data),
                        })
                        ... more code

Receive and decode data (Python):

def post(self, request, *args, **kwargs):
    response = Response(status.HTTP_400_BAD_REQUEST)
    try:
        enc = base64.b64decode(request.data['hash'])
        dec = settings. \
            RSA_SECRET_KEY.decrypt(enc,
                                   padding.OAEP(
                                       mgf=padding.MGF1(algorithm=hashes.SHA256()),
                                       algorithm=hashes.SHA256(),
                                       label=None
                                   )
                                   )
    except Exception as e:
        print(e)
    return response
0

1 Answer 1

1

encrypt() returns the ciphertext as an ArrayBuffer. However, btoa() requires a binary string. Therefore the ArrayBuffer must be converted to a binary string first (see ab2b64() below). With this fix a valid ciphertext is produced:

(async () => {

var pem = `-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAu0O0WXzs0bPuSx0n/b5B
zyqaFBgBC3U/9nfChj5idf6ae0NY9iDG749N/O5XB7dduAGq/UHemdvJTsztPWOd
y0NjVkluX0g6Mm+rB6rRAqihwq3xdl44OKvVWKmWM2iYhL6DahAdjMgvN4HSGU7c
HM0ZijDD89dQ2lfC7jEOloxzvja7P8ZCWM1A14VT48Z/F4uSyRkdV1SEY1ifhmSe
3dzxpR53RIblZcZsGV2Tic6pM9LDVaNiCMBLggbEy7ezvhut0wDyIM92McjNja99
8C/bhUzR4zaGPEWq1ZgiLiGM/iposa3O9JbOJ8g6yFKPArxVZfGDPcVYZhJEtNN8
QJzx1ZQdG22Nvybg8EHoESDh6Dd8SJ8yVySSnRn3N8QFANI5EblpcxlaCmb0rlpo
pmmNsx2Whz4oB9kMnWzQcOhmCeK9cbZOL9/cfPljY3Nyr18U5aGIygaTBVMJ3uTE
cbfGroEOLdDcv7zJuf1d4UsMOF0iioSAFvRxW57NHF8tlQaqKqPERAdg3c3Z0A0e
eFtxL3fKj7/PLC6/3C4ziNu0HLK0j5Ucyheczyl5bLDgUztRLSRuOjoNC1G0zYcK
DrnYNbJhuICYyyXL27ZMlIIS1j784jU3TRqP0+TtCr0vYOCiXC0lQA/I6J7QF9sB
itD59zlSvyMCl96prOAH5y8CAwEAAQ==
-----END PUBLIC KEY-----`;

var key = await importRsaKey(pem);
var ciphertext = await encrypt(key, 'The quick brown fox jumps over the lazy dog');
var base64String = ab2b64(ciphertext); // Fix: Convert ArrayBuffer to a binary string and Base64 decode!
document.getElementById("ciphertext").innerHTML = base64String; // e.g. ciphertext: IH+AikadAv7hdWJgKaLYTIgmN+Pvq4c5BU+VQi6irxCSI/IA30Tp6QK/rdNZYQDlNNJIk0XvK1AgbHPW3tBOygZjFGWVdVzDbYIhhva0o1FtHZluCIhxpJLEpAFKq+sMuBTtKeCqL8S4cS6B0GaDQzBjTh2D6k+p/fjVSx00bq4aqRUsgdYoa1uT3ook66qQfcyunImx++JrkjTSw7LB5XHjcGKXH3/92ZcxWyqBr5hIRje5Bdz6xPjpuGMlaPuOseVdyHEDcrAMl874NK16tz6ThkGw6rpwZxyEDdd13QOimVrFVpfpNleZVgunj+R+SFzDZYlHSAKwpte/RNvBe/V+cxuxaV7BqCM7xeU8hDzkPxE+IQS4Yimxxzy6b3y7KtyJN8nwjTECf7T76oigNYijNSYyKKWo0sf8/sfYET8HOElEzEH+m7Yblg784++308pRodz/8I4FolrDhORyJRiD46IDkDv47XsYtOBGgJzUlcjytNnlAvHGYDMXhVEVpMOIpuxXeNYyLvFgWaYcN8FEGgtUicXdbCinQvf2Fy6qF97IjWv2zaC/WbzbfnQl6ksiKKww1d8CawBoSzh7OibpujkK1QcyokbjPSOUJoE2IZvd1bGclrh3ypZRbqWNiRl4frCvcYRzfCO8pGkd0wUjdDI1g4kKvzgGt3lshT0=

async function importRsaKey(pem) {
    const pemHeader = "-----BEGIN PUBLIC KEY-----";
    const pemFooter = "-----END PUBLIC KEY-----";
    const pemContents = pem.substring(pemHeader.length, pem.length - pemFooter.length - 1);
    const binaryDerString = window.atob(pemContents);
    const binaryDer = str2ab(binaryDerString);
    var key = await crypto.subtle.importKey(
        "spki",
        binaryDer,
        {
            name: "RSA-OAEP",
            hash: "SHA-256"
        },
        true,
        ["encrypt"]
    );
    return key;
}

async function encrypt(key, msg) {
    return await crypto.subtle.encrypt(
        {name:"RSA-OAEP"},
        key,
        str2ab(msg)
    );
}

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 ab2b64(arrayBuffer) {
    return window.btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)));
}

})();
<p style="font-family:'Courier New', monospace;" id="ciphertext"></p>


Test:

The resulting ciphertext can be decrypted with Python:

from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
import base64

pkcs8Pem = '''-----BEGIN PRIVATE KEY-----
MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQC7Q7RZfOzRs+5L
HSf9vkHPKpoUGAELdT/2d8KGPmJ1/pp7Q1j2IMbvj0387lcHt124Aar9Qd6Z28lO
zO09Y53LQ2NWSW5fSDoyb6sHqtECqKHCrfF2Xjg4q9VYqZYzaJiEvoNqEB2MyC83
gdIZTtwczRmKMMPz11DaV8LuMQ6WjHO+Nrs/xkJYzUDXhVPjxn8Xi5LJGR1XVIRj
WJ+GZJ7d3PGlHndEhuVlxmwZXZOJzqkz0sNVo2IIwEuCBsTLt7O+G63TAPIgz3Yx
yM2Nr33wL9uFTNHjNoY8RarVmCIuIYz+Kmixrc70ls4nyDrIUo8CvFVl8YM9xVhm
EkS003xAnPHVlB0bbY2/JuDwQegRIOHoN3xInzJXJJKdGfc3xAUA0jkRuWlzGVoK
ZvSuWmimaY2zHZaHPigH2QydbNBw6GYJ4r1xtk4v39x8+WNjc3KvXxTloYjKBpMF
Uwne5MRxt8augQ4t0Ny/vMm5/V3hSww4XSKKhIAW9HFbns0cXy2VBqoqo8REB2Dd
zdnQDR54W3Evd8qPv88sLr/cLjOI27QcsrSPlRzKF5zPKXlssOBTO1EtJG46Og0L
UbTNhwoOudg1smG4gJjLJcvbtkyUghLWPvziNTdNGo/T5O0KvS9g4KJcLSVAD8jo
ntAX2wGK0Pn3OVK/IwKX3qms4AfnLwIDAQABAoICAAHxDEQnQu9TrcNSnJEJcXY7
61gM/anIP+8Gw9oPeIbfqmtfweLfaSCfvD/EmttmH88iGUtB7RRsTnSGNGmACGlM
nBGPdlj/jzbpqHzOXRdpdy/lDM1c4blYssAWFgwXaAlsTkGBxESq6K5rJqoDgs27
pKmlosp674gsA8XjdVLDRwnwWFWrcRGpoyP46mtAqh2s4Us7eu3mXu8GwrSqg2kq
esjq/XKU8XjyKznCGh8CKQf0Bflz1bbgg4foGQ9BqtfsQoufBWOoswGGIvd2m9gr
Ltv9dWmlLZQfZsuLJcOTrnoOJ4K8Ghq4G5AXB+D+1iPBnyMM837m9mkshFDZpn6i
dYmIKaE2DgNJSto10ZTo5kmve2rM7TaytvrTQqZbPXkP7pZO6O/bELtwvaZp0Op+
vhaUZWG5+C8gIZANv/1OAecMaDdVDbfIN+DK3ocinA6nYUE3yOBzgZkk8D4Z9A6Z
SuvBw8Pr5QNpUXiaD/OHzY++OvB2mv4mgEC67rtd8MyB63fpY+jYyE9v5D7CChgY
oLyEpdT+Uhtjfl5jLHj6xoF0Od2mc3PgL8yzTDpzfyGkjjkJknYJ76lxK+kSWvqi
v+34hCFjcQexZmd8wObTXz95/c+2W65eGAooeQn3SP8dmtuu1J/+T1mW6eMSuPf4
ckhb859P3S7APkq+Gr2lAoIBAQDOO458+lYP68d9Ho4ZouxkDUyZy1LDqiwvJ16/
X9gdDLBLgqSFvg6QyT7Gwj2rzjHTwwlKRzB/JvQH4M26omUEMTIcGQWHYr9XSwkj
Jh49MCpomOokDl/61n+UTy2et7nFzn5AEHm+vmjEUdDzEf8b3rzIL49kmzPX1Hu1
U9tMLzfTj4uW3nY3Ahn3goKBCM+mNktXRJqhw+1MkgW3eyffa2ums0CRb3nUnsdY
3rOAYZER2ukpbBI2vA4gKp1cgYBFehQhUBSup7HlRiwgQ3FSE4A3kFey1at2FpcZ
/epu5PafeaFYmgjCYE5IlL+/EKSPnkf8Q+Q+KrQk/oekUAZtAoIBAQDodFmqAaor
oRXgVeEOipE/fMssQGUOk2xZ8Yn9WwzPKGyhN498qooE2/RTHjngxmrrF/awGTq8
BiCyNPjVA0PW8TIUJxBX9155P+HxPpZZacpO3bYnWw4p24wizGJ1pgkAp7v1EWma
VlwewKcYx/ymMfyCCwUXbw6g4z1Gi2ni5VRzuawZ9nrbbM9wvSgkWKtC80EJ+ZDY
Tsr+5z4NqRAUZr61/HUiiBSu3UvH9089FcgelkELNYph/iaCmJnunIcFlEy5h/mA
yIwvuGagP+yk8yYXhALQ+S/zD3b+2dzvLRDtV7b93rtgfk3B/ZAD0Dzwc03zCLpP
dwWpE1JLK9KLAoIBAQCdnUjF3XD+0/zvc/W4RBsUUFG1zH3himIgW59+9VouwW7P
FvZ0PI/XOebfcr49WuYb6JhmC0hWNUgV6UpyFADOFmcssDbYhLCln3RJR62ep/wR
WqS/j7js9RgmGelMvy+crLcycSUKkW1ydPEThDKLc0ymVirqAe+6SOuO5prYe9HX
v4I4eKayXcnIrxbcVQaWCjLEbGsdrKbkeUkjNF2B1BA/JAn53M+onvzNv85CFM8R
bVP7U1wMNuc40DjZ5SNKdgWCfDiCTymXh2zb749g4gSA8rEDvWdAZf1vYO7Vd+nA
ce3M0FRXcdECiaSN+sM5/AcaFi0PEgYBrAGwo3R1AoIBAQCerbX7cFF6oOavEdCk
vYBzJzwGBBs3/PjM2S4KDdpLm1u0HZpMTpoSwRcimhKGVsvrmZsjEMXgTgqJu9FU
j3sCwfkeeqAUfF84Q5x3svKtLKMWfRB4AxdDCYS6yGw5xVKF6PpMS0ucOHF/6KDo
MLRNuveUyfL60SvaNeTBQC/S3BtvOAK8Yl3xZXChk+5QCVs3Q5hVN9BhaD/4C2B3
sL2yP4TV8/T90ojT6WpuoWqs1y6ZepYCEdVaGUSuh38kvCMLcvWA/Mob2Eqh1K3x
nFFtNDH/gXTus/vAXwEq7Qt9FXVlnyfiWuXr86wezXk+sSq4NO20BnQwBJ6PkQnv
GIYLAoIBABtXxV8hE9Jrk5K2jfPkGCbjs5rvVeUcTN5wMay4dHNblrTb9EIRiYPg
716yinJo71tvQRjvxBBkFjXYZ07ygebW5UmQdmvi4BkR+2gKn42sC5O6jvOQVNZY
ArLCgjK9QRQ/Sd5XT9yhR4/E5e3xF9Tn6/vEbaY24SgXDPe7gw6VOS+80eEVswx8
O7pEGfJSCySIPgFmn/2gTeQtkkNj5WSfl3dXYQJ+QQ8p5Eo+2O39hPXo7ATST5Aq
NI+6emgx79Ka/kfqRK0JjIb6cJLvSdsmM4APn4sZrVvRy9rSrZ+9drbNpnLua778
eWkk35z7vGmQT6CyzIhDJycFAIFkv6M=
-----END PRIVATE KEY-----'''

private_key = serialization.load_pem_private_key(
        pkcs8Pem.encode('utf-8'),
        password=None
    )

ciphertextFromJS = "IH+AikadAv7hdWJgKaLYTIgmN+Pvq4c5BU+VQi6irxCSI/IA30Tp6QK/rdNZYQDlNNJIk0XvK1AgbHPW3tBOygZjFGWVdVzDbYIhhva0o1FtHZluCIhxpJLEpAFKq+sMuBTtKeCqL8S4cS6B0GaDQzBjTh2D6k+p/fjVSx00bq4aqRUsgdYoa1uT3ook66qQfcyunImx++JrkjTSw7LB5XHjcGKXH3/92ZcxWyqBr5hIRje5Bdz6xPjpuGMlaPuOseVdyHEDcrAMl874NK16tz6ThkGw6rpwZxyEDdd13QOimVrFVpfpNleZVgunj+R+SFzDZYlHSAKwpte/RNvBe/V+cxuxaV7BqCM7xeU8hDzkPxE+IQS4Yimxxzy6b3y7KtyJN8nwjTECf7T76oigNYijNSYyKKWo0sf8/sfYET8HOElEzEH+m7Yblg784++308pRodz/8I4FolrDhORyJRiD46IDkDv47XsYtOBGgJzUlcjytNnlAvHGYDMXhVEVpMOIpuxXeNYyLvFgWaYcN8FEGgtUicXdbCinQvf2Fy6qF97IjWv2zaC/WbzbfnQl6ksiKKww1d8CawBoSzh7OibpujkK1QcyokbjPSOUJoE2IZvd1bGclrh3ypZRbqWNiRl4frCvcYRzfCO8pGkd0wUjdDI1g4kKvzgGt3lshT0="
decrypted = private_key.decrypt(
    base64.b64decode(ciphertextFromJS),
    padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),
    algorithm=hashes.SHA256(),
    label=None))

print(decrypted.decode('utf-8')) # The quick brown fox jumps over the lazy dog

A note about the encoding of the plaintext: Keep in mind that str2ab(msg) in encrypt() requires a string for msg where charCodeAt() returns a value less than 256, i.e. a single byte, for each character.
For the plaintext in the above example, this is satisfied. For arbitrary plaintexts, however, this is usually not true (e.g. € (U+20AC) equals 2 bytes). Therefore, to support arbitrary plaintexts, UTF-8 encoding should be used, e.g. new TextEncoder().encode(msg) instead of str2ab(msg).
For str2ab(binaryDerString) in importRsaKey() this is not critical, since binaryDerString is a binary string where each character corresponds to a single byte.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you! What a wonderful Christmas present. This worked perfectly.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.