Я запускаю приложение django и пытаюсь использовать «вызов» RSA, чтобы убедиться, что у пользователя есть правильный закрытый ключ. Ключи генерируются на стороне клиента с использованием js Web Crypto API, открытый ключ отправляется в django как jwk, а закрытый ключ хранится в файле pem на стороне клиента. Я хочу, чтобы представление django отправляло зашифрованный uuid на клиентскую страницу, где пользователь загрузил свой файл закрытого ключа. Затем страница локально расшифровывает uuid и отправляет его обратно на сервер для аутентификации. Кажется, у меня все заработало, кроме расшифровки на стороне клиента.
Вот соответствующая часть представления django/python:
uuid = secrets.token_hex(16)
key = json.loads(request.POST.get('key')) //key is in JWK format
e = int.from_bytes(base64.b64decode(base64url_to_base64(key['e'])), "big")
n = int.from_bytes(base64.b64decode(base64url_to_base64(key['n'])), "big")
rsakey = RSA.construct((n, e), consistency_check=True)
cipher = PKCS1_OAEP.new(rsakey, SHA256)
challenge = cipher.encrypt(uuid.encode())
challenge = base64.b64encode(challenge)
Вот соответствующая часть js на стороне клиента:
fileReader.readAsText(file);
fileReader.onload = function() {
filekey = fileReader.result;
filekey = filekey.substring(filekey.indexOf('-----BEGIN PRIVATE KEY-----'));
let pem = filekey;
const pemHeader = "-----BEGIN PRIVATE KEY-----";
const pemFooter = "-----END PRIVATE KEY-----";
const pemContents = pem.substring(pemHeader.length, pem.length - pemFooter.length);
const binaryDerString = window.atob(pemContents);
const binaryDer = str2ab(binaryDerString);
challenge = str2ab(window.atob(challenge.substring(2, challenge.length - 1));
console.info(challenge);
window.crypto.subtle.importKey(
"pkcs8",
binaryDer,
{
name: "RSA-OAEP",
hash: {name: "SHA-256"}
},
true,
["decrypt"]
).then((key) => {
const decryptedMessage = window.crypto.subtle.decrypt(
{
name: "RSA-OAEP"
},
key,
challenge
).then((txt) => {
console.info(txt);
console.info(ab2str(txt));
});
Когда я попробовал это, я получаю сообщение об ошибке Uncaught (in promise) DOMException: The operation failed for an operation-specific reason от window.crypto.subtle.decrypt(). Я думаю, что есть некоторая проблема с форматом зашифрованного текста, поскольку функция pycryptodome Crypto.PublicKey.PKCS1_OAEP.encrypt() принимает строку байтов, тогда как Web Crypto API использует объекты массива буферов. Однако у меня недостаточно опыта работы с типами данных js и python, чтобы разобраться. Также возможно, что это проблема с созданием ключа в python с использованием JWK.
Любая помощь или предложения будут с благодарностью!
Обновление: как было предложено @Topaco, вот некоторые тестовые данные:
открытый ключ jwk: {"alg":"RSA-OAEP-256","e":"AQAB","ext":true,"key_ops":["encrypt"],"kty":"RSA"," n":"tTW0HHD56Lv-FmDcucLOkBTCcT3ySRDtZ64MmsgFnZWGvCOAa3Q1kKYo8RAHWtjrvac_2enHF4LZlys2kS_j1kLZeyatsDWPuMDAHunRu-jscfQoSIODB1hc8YcPiG0vVLDEBY-VKozSOje6GcXWKcaYi4kFkbLmIIJgzHYoOccflAyXl_FvVHgcU5z5qYk8JjucZfqf9rzTH3HTaeCux2SMqJr6ubBmwX8-iwyC-4LBnnf27rdGL-DcMsOCq_CPWfgtx7nav9OCt51PdszsYx3JGLsbp0-iH1mSjKs4dg3ORh6KOyP2Qbq_HRALI__OKonp5FopApWSFvVDJZf3EhIHC2upnbpj-UCcjzkSSm0h3GkTr13GCqfhRz0jRvK-1Yj4PuwmXJ5kr1gxSbokqAnRL0oFicP_wTakvQOB7XpMWz2Cl3NDLDvqhocVMHZ9HwH52fD7k9IBYHXh6cVqeOwKSIy4whAyYFLmKg-57LwKB1diiSgi6MeCMG3NFafeEm3llooAmOTZZic_uD59-zfggywf6YOyBScilYBGWKxA9P-UVK76rxJIlwInDv7U1uY-8RodjPTNcGRw8RhvIyTkfgfpLRejXxAVEx0Xu-Gr4nZ2hPQgCQwP6pUL-ohN1Lz5Y0y6GWXrGcA2N1WY7GfBqrcNhWv7xXNVA1Qcei0"}
pem private key: -----BEGIN PRIVATE KEY----- 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 -----END PRIVATE KEY-----
plaintext/uuid: 060433466b8d0be7e7d435bdd43752b5
ciphertext after b64 encode in python: b'iN+uii2BAktcgSKnt2WLZ3baAJQxNYvD3AYTwumoTT+GYMKnydPVAT264XHbsHTzeLXQjIWPi1FT3Npa7PJUewDNMCKyzrZBLgyT6HOr6QIp0TJyPx83u1osPQw1clSf22ypOEeMc9JKuePDDVu1cH61gIYU4SoLMSpxf0oukVSa1Cg3Rficiscewtn36KBkBKY4M/CTde5lwiCf1x7ZtRboSsffZiUnD2dpRvZ0GcQugW7nV583mSVadtqfxdlVrXsa9hhnZhvBHuElHOByyH5GLICEKtHXZEraNTkMvnWFWpHrMz4xSX4O0c9JP2LD61t/8mnUwvHhutsxLSbULXBvcba8+e6+KYZsw14+Ol7h7235dPEYOXmLbNGU/KCQeznu9VaJMlN8CjF8MUfDPAOSIh8OcL74cKdh8U9CmhbFSOT+TjDYbvaXIFH0D3tY/nlg3C0O6uUSZmS+7qCHbNKGVvdDK0iagnWqu3+9i/hekFKP22SJtWPYw1d6d6w3vnqzwePPtFS7D8/14D4/0hjESOSpq2X1ZBhKBlatlma8HDOl9LEgxXs791fkoj4y5XevePW2r84OCuQL+6DtaUduLDWIoCLV+LnU/t8m5AuTDCEh7cpxYfgYeexggPzk7lEJtByKkrBHqC2Egwp0GCvnIZB+4oENfO+4ZqK2C4s='
Update: Was able to fix the issue, see my code blocks. The problem was that the python returns the b64 string surrounded by b'', which don't work with atob().
@Topaco Я изменил python на шифр = PKCS1_OAEP.new (rsakey, SHA256). Я не был уверен, какую кодировку использовать, кроме того, что ее нужно преобразовать в буфер массива. Я попытался выполнить этот пост: stackoverflow.com/questions/63595996/…, но когда я кодирую в b64 в python, а затем использую atob() перед преобразованием в arraybuffer, я получаю ошибку atob(): недопустимый символ в строке.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Чтобы решить мои проблемы, мне сначала пришлось использовать SHA256 в моем шифровании на стороне сервера:
Включить SHA256 из pycryptodome:
from Crypto.Hash import SHA256
Зашифровать с помощью SHA256:
cipher = PKCS1_OAEP.new(rsakey, SHA256)
challenge = cipher.encrypt(uuid.encode())
Далее я закодировал зашифрованный текст в base64 на сервере:
challenge = base64.b64encode(challenge)
Затем при подготовке зашифрованного текста к расшифровке на стороне клиента я использовал подстроку, чтобы вырезать b' и ' в начале и конце зашифрованного текста, а затем использовал atob() перед преобразованием в буфер массива:
challenge = str2ab(atob(challenge.substring(2,challenge.length - 1)));
PyCryptodome по умолчанию использует SHA-1 для OAEP. Почему
challengeна стороне JS закодирован в Base64? Если проблемы сохраняются, опубликуйте тестовые данные: тестовый ключ (открытый как JWK, закрытый как PEM), открытый текст, зашифрованный текст (challenge).