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.
GET/api/v1_0/cards/public-keyPełny kontrakt endpointu klucza publicznego RSA do szyfrowania danych kart.Pełny kontrakt w API Reference
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
Endpoint zwraca surowy klucz PEM jako tekst (nie JSON):
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----
Klucz jest w formacie PEM (X.509 SubjectPublicKeyInfo). Odczytaj treść odpowiedzi jako string - nie parsuj jej jako JSON.
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
| Pole | Nazwa | Opis | Format | Przykład |
|---|---|---|---|---|
PN | PAN (Primary Account Number) | Numer karty | Cyfry bez spacji | "4111111111111111" |
SC | Security Code | Kod CVV/CVC | 3 cyfry (4 dla Amex) | "123" |
DT | Date (Expiry) | Data ważności | MM/YY | "12/25" |
ID | Transaction ID | Identyfikator transakcji | UUID z rejestracji | "abc-def-123-456" |
TX | Timestamp | Czas operacji | Unix timestamp (sekundy) | 1700000000 |
Pole TX służy jako ochrona przed atakami typu replay. dpay.pl odrzuci zapytanie, jeśli timestamp jest starszy niż 600 sekund. Wyjątek: gdy płatność jest wykonywana po potwierdzeniu 3D Secure, akceptowane okno wynosi 3600 sekund. 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 (odpowiedź to surowy PEM jako tekst, nie JSON)
const response = await fetch(
'https://api-payments.dpay.pl/api/v1_0/cards/public-key'
);
const publicKey = await response.text();
// 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 (odpowiedź to surowy PEM jako tekst, nie JSON)
$ch = curl_init('https://api-payments.dpay.pl/api/v1_0/cards/public-key');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$publicKeyPem = curl_exec($ch);
curl_close($ch);
$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 (odpowiedź to surowy PEM jako tekst, nie JSON)
response = requests.get('https://api-payments.dpay.pl/api/v1_0/cards/public-key')
public_key_pem = response.text
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
- Szyfrowanie w przeglądarce - surowe dane karty powinny byc szyfrowane po stronie klienta (w JavaScript), zanim trafią na Twoj serwer
- Nigdy nie loguj danych kart - nie zapisuj numerow kart, CVV ani dat ważności w logach
- HTTPS obowiązkowe - cała strona musi byc dostępna przez HTTPS
- Nie przechowuj danych kart - nie zapisuj zaszyfrowanych ani surowych danych kart w bazie danych
- Aktualny timestamp - pole
TXmusi zawierac aktualny czas; dpay.pl odrzuci zapytania z timestampem starszym niż 600 sekund (3600 sekund, gdy płatność następuje po potwierdzeniu 3D Secure)
Nieprzestrzeganie zasad bezpieczeństwa może skutkowac utrata certyfikacji PCI DSS i odpowiedziałnością prawna za wycieki danych kart płatniczych.