Przejdź do głównej zawartości

Szyfrowanie danych kart (RSA/PKCS#1)

W przypadku płatności kartowych Server-to-Server (S2S) dane karty muszą byc zaszyfrowane kluczem publicznym RSA przed wysłaniem do dpay.pl. Ten przewodnik opisuje szczegółowo cały proces szyfrowania.

Dlaczego szyfrowanie jest wymagane?

Surowe dane karty (numer, CVV, data ważności) to dane wrażliwe objęte standardem PCI DSS. Szyfrowanie RSA zapewnia, ze:

  • Dane karty sa czytelne wyłącznie dla dpay.pl (tylko dpay.pl posiada klucz prywatny)
  • Nawet jeśli dane zostana przejete w tranzycie, będą bezużyteczne bez klucza prywatnego
  • Twoj serwer nigdy nie ma dostępu do surowych danych karty (szyfrowanie odbywa się w przeglądarce klienta)

Algorytm szyfrowania

  • Algorytm: RSA
  • Padding: PKCS#1 v1.5
  • Kodowanie wyniku: Base64

Krok 1: Pobranie klucza publicznego

GET https://api-payments.dpay.pl/api/v1_0/cards/public-key

Odpowiedz

{
"publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...\n-----END PUBLIC KEY-----"
}

Klucz jest w formacie PEM (X.509 SubjectPublicKeyInfo).

wskazówka

Klucz publiczny może być rotowany bez zapowiedzi. Przed każdą transakcją, pobierz nowy klucz publiczny.

Krok 2: Przygotowanie obiektu JSON

Przygotuj obiekt JSON z danymi karty i metadanymi transakcji:

{
"PN": "4111111111111111",
"SC": "123",
"DT": "12/25",
"ID": "abc-def-123-456",
"TX": 1700000000
}

Opis pol

PoleNazwaOpisFormatPrzykład
PNPAN (Primary Account Number)Numer kartyCyfry bez spacji"4111111111111111"
SCSecurity CodeKod CVV/CVC3 cyfry (4 dla Amex)"123"
DTDate (Expiry)Data ważnościMM/YY"12/25"
IDTransaction IDIdentyfikator transakcjiUUID z rejestracji"abc-def-123-456"
TXTimestampCzas operacjiUnix timestamp (sekundy)1700000000
Pole TX

Pole TX służy jako ochrona przed atakami typu replay. dpay.pl odrzuci zapytanie, jeśli timestamp jest starszy niz kilka minut. Zawsze użyj aktualnego czasu.

Krok 3: Szyfrowanie

TypeScript / JavaScript (JSEncrypt)

Biblioteka JSEncrypt to najpopularniejszy wybór do szyfrowania RSA w przeglądarce:

npm install jsencrypt
import JSEncrypt from 'jsencrypt';

interface CardData {
PN: string; // Numer karty
SC: string; // CVV
DT: string; // Data ważności MM/YY
ID: string; // Transaction ID
TX: number; // Unix timestamp
}

async function encryptCardData(
cardNumber: string,
cvv: string,
expiryDate: string,
transactionId: string
): Promise<string> {
// 1. Pobierz klucz publiczny
const response = await fetch(
'https://api-payments.dpay.pl/api/v1_0/cards/public-key'
);
const { publicKey } = await response.json();

// 2. Przygotuj obiekt z danymi karty
const cardData: CardData = {
PN: cardNumber.replace(/\s/g, ''), // Usun spacje
SC: cvv,
DT: expiryDate, // Format: MM/YY
ID: transactionId,
TX: Math.floor(Date.now() / 1000),
};

// 3. Zaszyfruj RSA/PKCS#1
const encrypt = new JSEncrypt();
encrypt.setPublicKey(publicKey);

const encrypted = encrypt.encrypt(JSON.stringify(cardData));
if (!encrypted) {
throw new Error('Szyfrowanie danych karty nie powiodło sie');
}

// 4. Wynik jest juz w Base64
return encrypted;
}

// === Przykład użycia ===

const encryptedData = await encryptCardData(
'4111 1111 1111 1111',
'123',
'12/25',
'abc-def-123-456'
);

// Wyślij encryptedData do serwera
await fetch('/api/pay/card', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ encryptedCardData: encryptedData }),
});

PHP (OpenSSL)

Szyfrowanie w PHP za pomoca wbudowanego rozszerzenia OpenSSL:

<?php
/**
* Szyfrowanie danych karty RSA/PKCS#1
*
* UWAGA: W produkcji szyfrowanie powinno odbywac się po stronie klienta
* (w przeglądarce), aby surowe dane karty nigdy nie trafiły na Twoj serwer.
* Ten przykład służy do celów demonstracyjnych i testowych.
*/
function encryptCardData(
string $cardNumber,
string $cvv,
string $expiryDate,
string $transactionId,
string $publicKeyPem
): string {
// 1. Przygotuj obiekt JSON
$cardData = json_encode([
'PN' => preg_replace('/\s/', '', $cardNumber),
'SC' => $cvv,
'DT' => $expiryDate,
'ID' => $transactionId,
'TX' => time(),
], JSON_UNESCAPED_SLASHES);

// 2. Zaladuj klucz publiczny
$publicKey = openssl_pkey_get_public($publicKeyPem);
if (!$publicKey) {
throw new RuntimeException(
'Nie można zaladowac klucza publicznego: ' . openssl_error_string()
);
}

// 3. Zaszyfruj RSA/PKCS#1
$encrypted = '';
$result = openssl_public_encrypt(
$cardData,
$encrypted,
$publicKey,
OPENSSL_PKCS1_PADDING
);

if (!$result) {
throw new RuntimeException(
'Szyfrowanie nie powiodło sie: ' . openssl_error_string()
);
}

// 4. Zakoduj w Base64
return base64_encode($encrypted);
}

// === Przykład użycia ===

// Pobierz klucz publiczny
$ch = curl_init('https://api-payments.dpay.pl/api/v1_0/cards/public-key');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

$publicKeyPem = $response['publicKey'];

$encryptedCardData = encryptCardData(
'4111111111111111',
'123',
'12/25',
'abc-def-123-456',
$publicKeyPem
);

echo $encryptedCardData; // Base64-encoded encrypted data

Python (pycryptodome)

pip install pycryptodome
import json
import time
import base64
import re
import requests
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5


def encrypt_card_data(
card_number: str,
cvv: str,
expiry_date: str,
transaction_id: str,
public_key_pem: str,
) -> str:
"""Szyfrowanie danych karty RSA/PKCS#1."""

# 1. Przygotuj obiekt JSON
card_data = json.dumps({
'PN': re.sub(r'\s', '', card_number),
'SC': cvv,
'DT': expiry_date,
'ID': transaction_id,
'TX': int(time.time()),
})

# 2. Zaladuj klucz publiczny
key = RSA.import_key(public_key_pem)

# 3. Zaszyfruj RSA/PKCS#1 v1.5
cipher = PKCS1_v1_5.new(key)
encrypted = cipher.encrypt(card_data.encode('utf-8'))

# 4. Zakoduj w Base64
return base64.b64encode(encrypted).decode('utf-8')


# === Przykład użycia ===

# Pobierz klucz publiczny
response = requests.get('https://api-payments.dpay.pl/api/v1_0/cards/public-key')
public_key_pem = response.json()['publicKey']

encrypted = encrypt_card_data(
'4111111111111111',
'123',
'12/25',
'abc-def-123-456',
public_key_pem,
)

print(encrypted)

Java

import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class CardEncryption {

public static String encryptCardData(
String cardNumber,
String cvv,
String expiryDate,
String transactionId,
String publicKeyPem
) throws Exception {

// 1. Przygotuj JSON
String cardData = String.format(
"{\"PN\":\"%s\",\"SC\":\"%s\",\"DT\":\"%s\",\"ID\":\"%s\",\"TX\":%d}",
cardNumber.replaceAll("\\s", ""),
cvv,
expiryDate,
transactionId,
System.currentTimeMillis() / 1000
);

// 2. Zaladuj klucz publiczny
String keyContent = publicKeyPem
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s", "");

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

// 3. Zaszyfruj RSA/PKCS#1
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encrypted = cipher.doFinal(cardData.getBytes("UTF-8"));

// 4. Zakoduj w Base64
return Base64.getEncoder().encodeToString(encrypted);
}
}

Walidacja danych karty przed szyfrowaniem

Przed szyfrowaniem warto zwalidowac dane karty po stronie klienta:

Algorytm Luhna (walidacja numeru karty)

function isValidCardNumber(number) {
const digits = number.replace(/\s/g, '');
if (!/^\d{13,19}$/.test(digits)) return false;

let sum = 0;
let isEven = false;

for (let i = digits.length - 1; i >= 0; i--) {
let digit = parseInt(digits[i], 10);
if (isEven) {
digit *= 2;
if (digit > 9) digit -= 9;
}
sum += digit;
isEven = !isEven;
}

return sum % 10 === 0;
}

Walidacja daty ważności

function isValidExpiryDate(date) {
const match = date.match(/^(\d{2})\/(\d{2})$/);
if (!match) return false;

const month = parseInt(match[1], 10);
const year = parseInt('20' + match[2], 10);

if (month < 1 || month > 12) return false;

const now = new Date();
const expiry = new Date(year, month); // Pierwszy dzien następnego miesiaca

return expiry > now;
}

Walidacja CVV

function isValidCVV(cvv) {
return /^\d{3,4}$/.test(cvv);
}

Bezpieczeństwo - najlepsze praktyki

  1. Szyfrowanie w przeglądarce - surowe dane karty powinny byc szyfrowane po stronie klienta (w JavaScript), zanim trafią na Twoj serwer
  2. Nigdy nie loguj danych kart - nie zapisuj numerow kart, CVV ani dat ważności w logach
  3. HTTPS obowiązkowe - cała strona musi byc dostępna przez HTTPS
  4. Nie przechowuj danych kart - nie zapisuj zaszyfrowanych ani surowych danych kart w bazie danych
  5. Aktualny timestamp - pole TX musi zawierac aktualny czas; dpay.pl odrzući zapytania ze starym timestampem
ważne

Nieprzestrzeganie zasad bezpieczeństwa może skutkowac utrata certyfikacji PCI DSS i odpowiedziałnością prawna za wycieki danych kart płatniczych.