Szyfrowanie E2E treści faktury XML w KSeF
| Przedmiot audytu | api.ksef.mf.gov.pl — KSeF 2.0, środowisko produkcyjne |
| Metoda | Analiza specyfikacji OpenAPI + dokumentacji technicznej MF |
| Data | Marzec 2026 |
| Autor | Grzegorz GPS Świderski |
| Interwencja poselska | Posłowie Skalik i Zawiślak, 20 lutego 2026 r. |
1. Kontekst i pytanie audytowe
Niniejszy audyt stanowi odpowiedź na Pytanie 2 z interwencji poselskiej złożonej przez posłów Skalika i Zawiślaka w Ministerstwie Finansów dnia 20 lutego 2026 r. Poprzednie dwa audyty wykazały:
- Połączenie HTTPS kierowane do
api.ksef.mf.gov.plulega terminacji TLS na infrastrukturze zewnętrznego operatora WAF — firmy Imperva (https://www.mpolska24.pl/post/21091/audyt-infrastruktury-sieciowej-bramka-waf-systemu-ksef-20). - Metadane faktur w formacie JSON (NIP-y, kwoty, daty, nazwy firm) przepływają przez warstwę Impervy w postaci jawnej, bez szyfrowania aplikacyjnego (https://www.mpolska24.pl/post/21092/audyt-techniczny-jawnosc-metadanych-faktur-ksef-w-json-na-bramce-waf-obslugiwanej-przez-imperve).
2. Metodologia i zakres
Audyt oparty jest wyłącznie na publicznie dostępnych, oficjalnych źródłach opublikowanych przez Ministerstwo Finansów. Nie zastosowano żadnych metod ingerencji w system.
| Krok | Źródło | Cel | Wynik |
|---|---|---|---|
| 1 | open-api.json (GitHub MF) | Lista endpointów i schematów API | ✅ |
| 2 | open-api.json — schemat SendInvoiceRequest | Struktura żądania wysyłki faktury XML | ✅ |
| 3 | sesja-interaktywna.md (GitHub MF) | Mechanizm kryptograficzny sesji online | ✅ |
| 4 | sesja-wsadowa.md (GitHub MF) | Mechanizm kryptograficzny sesji wsadowej | ✅ |
| 5 | open-api.json — schemat EncryptionInfo | Schemat klucza szyfrującego | ✅ |
3. Krok 1 — Pobranie specyfikacji OpenAPI i identyfikacja endpointów
3.1. Komenda
$raw = Invoke-WebRequest -Uri $specUrl | Select-Object -ExpandProperty Content
$spec = $raw | ConvertFrom-Json
Write-Host "=== ŚCIEŻKI API ==="
$spec.paths.PSObject.Properties.Name | Sort-Object
Write-Host "`n=== SCHEMATY ZWIĄZANE Z WYSYŁKĄ ==="
$spec.components.schemas.PSObject.Properties.Name |
Select-String -Pattern 'Invoice|Send|Encrypt|Payload|Hash' -CaseSensitive:$false
3.2. Wynik (fragment)
/sessions/online
/sessions/online/{referenceNumber}/invoices
/sessions/batch
/invoices/query/metadata
/security/public-key-certificates
...
=== SCHEMATY ZWIĄZANE Z WYSYŁKĄ ===
EncryptionInfo
SendInvoiceRequest
SendInvoiceResponse
InvoiceMetadata
InvoicePackage
InvoicePackagePart
Sha256HashBase64
3.3. Interpretacja
Specyfikacja OpenAPI API KSeF 2.0 jest publicznie dostępna w oficjalnym repozytorium GitHub Ministerstwa Finansów (CIRFMF/ksef-docs). Zidentyfikowano dwa krytyczne schematy: SendInvoiceRequest (żądanie wysyłki faktury) oraz EncryptionInfo (informacje o kluczu szyfrującym). Ich szczegółowa analiza następuje w kolejnych krokach.
4. Krok 2 — Schemat żądania wysyłki faktury XML (SendInvoiceRequest)
4.1. Komenda
$spec = Invoke-WebRequest -Uri $specUrl | Select-Object -ExpandProperty Content | ConvertFrom-Json
Write-Host "=== SCHEMAT: SendInvoiceRequest ==="
$spec.components.schemas.SendInvoiceRequest | ConvertTo-Json -Depth 10
4.2. Wynik
"required": [
"encryptedInvoiceContent",
"encryptedInvoiceHash",
"encryptedInvoiceSize",
"invoiceHash",
"invoiceSize"
],
"properties": {
"invoiceHash": {
"description": "Skrót SHA256 oryginalnej faktury, zakodowany w formacie Base64."
},
"encryptedInvoiceHash": {
"description": "Skrót SHA256 zaszyfrowanej faktury, zakodowany w formacie Base64."
},
"encryptedInvoiceContent": {
"type": "string",
"description": "Faktura zaszyfrowana algorytmem AES-256-CBC z dopełnianiem PKCS#7
(kluczem przekazanym przy otwarciu sesji), zakodowana w formacie Base64.",
"format": "byte"
}
}
}
4.3. Interpretacja
Kontrakt API nie zawiera pola z jawną treścią faktury XML. Jedynym polem przenoszącym treść dokumentu jest encryptedInvoiceContent, opisane w specyfikacji oficjalnie przez MF jako:
API KSeF jest zaprojektowane tak, aby nigdy nie przyjmować jawnego XML faktury. Jedyną legalną formą przesłania faktury jest jej zaszyfrowana wersja. Mechanizm szyfrowania jest architektonicznie wymuszony przez kontrakt API.
5. Krok 3 — Mechanizm kryptograficzny sesji interaktywnej
5.1. Komenda
$content = Invoke-WebRequest -Uri $url | Select-Object -ExpandProperty Content
$content | Select-String -Pattern 'encrypt|szyfr|klucz|key|AES|RSA' -CaseSensitive:$false -Context 2,2
5.2. Wynik — kluczowy fragment dokumentacji MF
* wygenerowanie klucza symetrycznego o długości 256 bitów i wektora inicjującego
o długości 128 bitów (IV), dołączanego jako prefiks do szyfrogramu,
* zaszyfrowanie dokumentu algorytmem AES-256-CBC z dopełnianiem PKCS#7,
* zaszyfrowanie klucza symetrycznego algorytmem RSAES-OAEP
(padding OAEP z funkcją MGF1 opartą na SHA-256 oraz skrótem SHA-256),
przy użyciu klucza publicznego KSeF Ministerstwa Finansów.
-- Przykład C# (biblioteka referencyjna MF):
byte[] encryptedInvoice = cryptographyService.EncryptBytesWithAES256(
invoice, encryptionData.CipherKey, encryptionData.CipherIv);
SendInvoiceRequest sendOnlineInvoiceRequest = SendInvoiceOnlineSessionRequestBuilder
.Create()
.WithEncryptedDocumentContent(Convert.ToBase64String(encryptedInvoice))
.Build();
5.3. Interpretacja
Dokumentacja techniczna MF opisuje trójstopniowy mechanizm kryptograficzny wykonywany po stronie klienta, przed wysyłką jakichkolwiek danych do sieci:
| Etap | Operacja | Szczegóły techniczne |
|---|---|---|
| 1 | Generowanie klucza | Klient generuje lokalnie losowy klucz AES-256 i wektor IV — nigdy nie opuszczają klienta w postaci jawnej |
| 2 | Szyfrowanie XML | Faktura XML szyfrowana lokalnie: AES-256-CBC/PKCS#7. W sieci pojawia się wyłącznie zaszyfrowany blob |
| 3 | Ochrona klucza AES | Klucz AES szyfrowany kluczem publicznym RSA-OAEP/SHA-256 MF. Odszyfrowanie możliwe wyłącznie przez posiadacza klucza prywatnego MF |
6. Krok 4 — Mechanizm kryptograficzny sesji wsadowej
6.1. Komenda
$content = Invoke-WebRequest -Uri $url | Select-Object -ExpandProperty Content
$content | Select-String -Pattern 'AES|RSA|zaszyfrowa|klucz|encrypt' -CaseSensitive:$false -Context 1,2
6.2. Wynik — kluczowy fragment dokumentacji MF
* wygenerowanie klucza symetrycznego o długości 256 bitów i wektora inicjującego
o długości 128 bitów (IV), dołączanego jako prefiks do szyfrogramu,
* przygotowanie paczki ZIP,
* zaszyfrowanie części algorytmem AES-256-CBC z dopełnianiem PKCS#7,
* zaszyfrowanie klucza symetrycznego algorytmem RSAES-OAEP
(padding OAEP z funkcją MGF1 opartą na SHA-256 oraz skrótem SHA-256),
przy użyciu klucza publicznego KSeF Ministerstwa Finansów.
-- Przykład Java (biblioteka referencyjna MF):
byte[] encryptedZipPart = defaultCryptographyService.encryptBytesWithAES256(
zipParts.get(i), cipherKey, cipherIv);
6.3. Interpretacja
Sesja wsadowa stosuje identyczny mechanizm kryptograficzny co sesja interaktywna, z jedną różnicą architektoniczną: zamiast pojedynczej faktury szyfrowany jest plik ZIP zawierający wiele faktur XML. Klucz AES jest szyfrowany tym samym kluczem publicznym RSA MF. Wniosek jest tożsamy — treść faktur XML jest chroniona przed odczytem przez warstwę pośredniczącą.
7. Krok 5 — Schemat EncryptionInfo i endpoint klucza publicznego
7.1. Komenda
Write-Host "=== SCHEMAT: EncryptionInfo ==="
$spec.components.schemas.EncryptionInfo | ConvertTo-Json -Depth 10
Write-Host "`n=== ENDPOINT KLUCZA PUBLICZNEGO ==="
$spec.paths.'/security/public-key-certificates' | ConvertTo-Json -Depth 5
7.2. Wynik
{
"required": ["encryptedSymmetricKey", "initializationVector"],
"properties": {
"encryptedSymmetricKey": {
"description": "Klucz symetryczny o długości 32 bajtów, zaszyfrowany algorytmem RSA
(Padding: OAEP z SHA-256), zakodowany w formacie Base64.
[Klucz publiczny Ministerstwa Finansów]",
"format": "byte"
},
"initializationVector": {
"description": "Wektor inicjalizujący (IV) o długości 16 bajtów,
używany do szyfrowania symetrycznego, zakodowany w formacie Base64.",
"format": "byte"
}
}
}
=== ENDPOINT KLUCZA PUBLICZNEGO ===
GET /security/public-key-certificates
-- Opis: Pobranie aktualnych certyfikatów klucza publicznego KSeF MF
7.3. Interpretacja
Schemat EncryptionInfo potwierdza, że przy otwarciu sesji klient przekazuje wyłącznie zaszyfrowany klucz AES (encryptedSymmetricKey) — nigdy sam klucz w postaci jawnej. Klucz publiczny RSA do szyfrowania tego klucza AES jest pobierany z dedykowanego, publicznego endpointu GET /security/public-key-certificates. Odwrotna operacja — odszyfrowanie klucza AES, a tym samym faktury XML — wymaga klucza prywatnego RSA, który powinien być przechowywany wyłącznie po stronie Ministerstwa Finansów.
8. Kwestia otwarta — lokalizacja klucza prywatnego RSA
Audyt dokumentacyjny potwierdza poprawność architektury kryptograficznej po stronie klienta. Istnieje jednak kwestia, której niniejszy audyt — z definicji oparty na publicznie dostępnych źródłach — nie może rozstrzygnąć:
Gdzie fizycznie jest przechowywany klucz prywatny RSA Ministerstwa Finansów, który jako jedyny umożliwia odszyfrowanie klucza AES sesji, a tym samym treści faktury XML?
Audyt 1 wykazał obecność dodatkowej warstwy infrastrukturalnej zidentyfikowanej jako Microsoft Azure Application Gateway v2 — obecnej między Impervą a backendem Kestrel. Nie jest publicznie wiadome, czy odszyfrowanie RSA następuje dopiero w backendzie Kestrel (MF), czy też wcześniej — w warstwie Azure Application Gateway.
Kluczowe pytanie: czy klucz prywatny RSA MF jest przechowywany w dedykowanym HSM znajdującym się wyłącznie pod fizyczną kontrolą MF, czy też w Azure Key Vault lub innym mechanizmie dostępnym dla warstwy Azure?
Rozstrzygnięcie tej kwestii wymagałoby: (a) dokumentacji wewnętrznej MF dotyczącej zarządzania kluczami (KMS/HSM), lub (b) odpowiedzi MF na Pytanie 4 z interwencji poselskiej, które wprost pyta o dostęp podmiotów zewnętrznych do kluczy kryptograficznych.
9. Podsumowanie — mapa kryptograficzna KSeF
Na podstawie przeprowadzonych pięciu kroków audytowych można sporządzić kompletną mapę ochrony kryptograficznej w systemie KSeF 2.0:
| Przepływ danych | Format | Szyfrowanie E2E | Widoczność dla Impervy |
|---|---|---|---|
| Wysyłka faktury (sesja online) | XML → AES-Base64 | ✅ AES-256-CBC + RSA-OAEP | ❌ Zaszyfrowany blob |
| Wysyłka wsadowa (ZIP) | ZIP → AES | ✅ AES-256-CBC + RSA-OAEP | ❌ Zaszyfrowany blob |
| Pobieranie faktur (eksport) | ZIP szyfrowany | ✅ Kluczem żądającego | ❌ Zaszyfrowany blob |
| Zapytanie o metadane | JSON (jawny) | ❌ Brak | ✅ Pełna jawność |
10. Interpretacja wyników
10.1. Co wykazał audyt
Oficjalna dokumentacja techniczna Ministerstwa Finansów — specyfikacja OpenAPI oraz instrukcje integracyjne dla sesji online i wsadowej — jednoznacznie potwierdza istnienie szyfrowania aplikacyjnego E2E dla treści faktury XML. Szyfrowanie jest:
- Klienckie — wykonywane po stronie nadawcy, przed wysyłką danych do sieci.
- Symetryczne dla treści — AES-256-CBC z dopełnianiem PKCS#7.
- Asymetryczne dla klucza — klucz AES szyfrowany RSA-OAEP/SHA-256 kluczem publicznym MF.
- Architektonicznie wymuszone — API nie przyjmuje jawnego XML; kontrakt wymaga
encryptedInvoiceContent.
10.2. Co audyt wyklucza
Audyt wyklucza scenariusz, w którym treść XML faktury jest dostępna dla Impervy w postaci jawnej w standardowym modelu operacyjnym. Imperva przetwarza zaszyfrowany blob — nie może go odczytać bez klucza prywatnego RSA MF.
10.3. Asymetria ochrony — kontrast z metadanymi JSON
Kluczowe odkrycie audytu to nie samo potwierdzenie szyfrowania XML, lecz dramatyczny kontrast z metadanymi JSON:
- Treść faktury XML — chroniona silnym szyfrowaniem E2E. Imperva nie może jej odczytać.
- Metadane JSON — NIP-y, kwoty, nazwy firm, daty — bez żadnej ochrony aplikacyjnej. Imperva ma do nich pełny dostęp.
MF zaprojektowało ochronę treści prawnej faktury (XML), jednocześnie nie chroniąc danych biznesowych o wysokiej wartości wywiadowczej (metadanych JSON). Z perspektywy wywiadu gospodarczego metadane mogą być wartościowsze niż sama treść dokumentu — umożliwiają masową analizę przepływów finansowych między podmiotami bez konieczności odczytywania treści faktur.
11. Wnioski końcowe
| Nr | Pytanie | Odpowiedź audytu |
|---|---|---|
| P1 | Terminacja TLS u WAF Impervy | ✅ Potwierdzona — rekonesans sieciowy (CNAME, X-CDN, bloki IP, aktywna filtracja) |
| P2a | Szyfrowanie E2E treści XML | ✅ Potwierdzone — AES-256-CBC po stronie klienta, klucz RSA-OAEP do MF. XML nieczytelny dla Impervy. |
| P2b | Lokalizacja klucza prywatnego RSA MF | ⚠️ Niezbadana — publiczne źródła nie ujawniają, czy klucz jest w HSM pod kontrolą MF czy w infrastrukturze Azure. |
| P3 | Jawność metadanych JSON na WAF | ✅ Potwierdzona — NIP-y, kwoty, nazwy firm, daty przepływają przez Impervę w postaci jawnej. |
11.1. Rekomendacja dla posłów — uzupełniające pytanie do MF
Na podstawie wyników audytu rekomendowane jest uzupełnienie interwencji poselskiej o następujące pytanie precyzujące:
Gdzie fizycznie jest przechowywany klucz prywatny RSA służący do odszyfrowania klucza symetrycznego AES w sesji KSeF? Czy klucz ten jest przechowywany wyłącznie w sprzętowym module HSM znajdującym się pod bezpośrednią, fizyczną kontrolą Ministerstwa Finansów, czy też w usłudze Azure Key Vault lub innym mechanizmie dostępnym dla warstwy Microsoft Azure Application Gateway ujawnionej przez audyt infrastrukturalny?
Odpowiedź na to pytanie rozstrzyga, czy szyfrowanie E2E treści faktury XML jest faktycznie skuteczne wobec podmiotów trzecich kontrolujących infrastrukturę Azure.
11.2. Ocena odpowiedzi Ministerstwa Finansów na interwencję poselską
MF odpowiedziało, że „detale operacyjne, informacje dotyczące architektury systemu oraz szczegóły współpracy z dostawcami pozostają objęte ograniczeniami informacyjnymi ze względów bezpieczeństwa". W świetle niniejszego audytu ocena tej odpowiedzi jest następująca:
- Odpowiedź MF jest technicznie zbędna dla Pytania 2 — architektura kryptograficzna jest opisana w publicznie dostępnej dokumentacji MF na GitHub. MF odmówiło odpowiedzi na pytanie, na które odpowiedź jest jawna.
- Odmowa ujawnienia lokalizacji klucza prywatnego RSA (Pytanie 4) jest uzasadniona ze względów bezpieczeństwa, jednak rodzi ona uzasadnione wątpliwości co do rzeczywistej suwerenności infrastrukturalnej MF.
- Odpowiedź MF całkowicie pomija kwestię metadanych JSON (Pytanie 3), dla których nie istnieje żadna techniczna bariera ochrony przed odczytem przez Impervę.