Skip to main content

Шифрування пароля за допомогою відкритого ключа RSA

Приклад для Postman (js)

Функції для шифрування

/**
 * Encrypt data with RSA public key
 * @param publicKey - get from server
 * @param password - user key password
 */
utils = {
    rsaEncrypt: async function(publicKey, password) {
        const cryptoKey = await crypto.subtle.importKey("spki", publicKey, {
            name: "RSA-OAEP",
            hash: "SHA-256"
        }, true, ["encrypt"]);
        const encodedText = new TextEncoder().encode(password);
        const encrypted = await crypto.subtle.encrypt({name: "RSA-OAEP"}, cryptoKey, encodedText);
        //
        return toBase64(encrypted);
    }
}

function toBase64(buffer) {
    const bytes = new Uint8Array(buffer);
    let binary = '';
    for (let i = 0; i < bytes.byteLength; i++) {
        binary += String.fromCharCode(bytes[i]);
    }
    return btoa(binary);
}

Шифрування пароля

const encrypted = await utils.rsaEncrypt(Uint8Array.from(publicKey), password));

де, 

  • publicKey - публічний ключ, отриманий методом GET /api/external/key
  • password - пароль від ключа

Приклад Java

import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;

String pem = """
-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB...
-----END PUBLIC KEY-----
""";

String body = pem
        .replace("-----BEGIN PUBLIC KEY-----", "")
        .replace("-----END PUBLIC KEY-----", "")
        .replaceAll("\\s", "");

byte[] der = Base64.getDecoder().decode(body);
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(der));

OAEPParameterSpec oaepParams = new OAEPParameterSpec(
        "SHA-256",
        "MGF1",
        MGF1ParameterSpec.SHA256,
        PSource.PSpecified.DEFAULT
);

Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey, oaepParams);

byte[] encrypted = cipher.doFinal("my-secret-password".getBytes(StandardCharsets.UTF_8));
String encryptedBase64 = Base64.getEncoder().encodeToString(encrypted);

Приклад Node.js

import crypto from "node:crypto";

const publicKeyPem = `-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB...
-----END PUBLIC KEY-----`;

const encrypted = crypto.publicEncrypt(
  {
    key: publicKeyPem,
    padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
    oaepHash: "sha256"
  },
  Buffer.from("my-secret-password", "utf8")
);

const encryptedBase64 = encrypted.toString("base64");

Як шифрувати?

  1. Отримати публічний ключ через /api/external/key?type=pem.
  2. Імпортувати public key у crypto-бібліотеку як RSA public key.
  3. Зашифрувати plaintext пароль bytes.
  4. Результат RSA encryption закодувати в base64.
  5. Передати base64 string у відповідне поле запиту.
plaintext password
  -> UTF-8 bytes
  -> RSA encrypt with server public key
  -> base64
  -> JSON field

алгоритм шифрування має відповідати вимогам API: RSA/ECB/OAEPPadding з OAEP SHA-256 і MGF1 SHA-256;
- не використовуйте RSA_PKCS1_PADDING / RSA/ECB/PKCS1Padding — сервер не зможе розшифрувати такі дані;
- RSA OAEP має обмеження на довжину plaintext, яке залежить від розміру ключа; передавайте короткі секрети/паролі, а не великі JSON/файли;
- публічний ключ має TTL, тому його треба оновлювати після expiredAt / x-key-ttl;
- не кешуйте ключ назавжди;
- якщо сервер повертає decrypt_error для caPassPhrase або pkPassword, найчастіші причини:
  - використаний застарілий public key;
  - зашифровано не тим padding/OAEP hash;
  - у JSON переданий не base64 від encrypted bytes;
  - plaintext був зашифрований іншим ключем.