Module: Ksef::Crypto

Defined in:
lib/ksef/crypto.rb,
lib/ksef/crypto/digest.rb,
lib/ksef/crypto/encryptor.rb,
lib/ksef/crypto/certificate.rb,
lib/ksef/crypto/public_keys.rb

Overview

Encryption of the payloads KSeF requires to be encrypted (docs/REFERENCE.md §10).

Every parameter here is ledgered, not chosen. They come from sesja-interaktywna.md and uwierzytelnianie.md §2.2 — first-tier documentation — corroborated against both reference clients' cryptography services. Do not adjust one without re-reading §10 first; a mismatch here does not fail loudly, it produces a payload KSeF silently cannot decrypt.

What lives where

This module holds the primitives: the algorithm identifiers, base64, SHA-256 and the one RSA operation. Certificate and PublicKeys deal with the keys the Ministry publishes, Encryptor with a session's symmetric key, and Digest with the integrity metadata that travels beside every payload.

Nothing here knows an API field name. Mapping to EncryptionInfo and SendInvoiceRequest is Encryptor's single concession to the wire format, and building the requests themselves belongs to the session layer.

Defined Under Namespace

Classes: Certificate, Digest, Encryptor, PublicKeys

Constant Summary collapse

CIPHER =

AES-256-CBC with PKCS#7 padding: 256-bit key, 128-bit IV, 128-bit block (§10.1). The Java client names the same thing AES/CBC/PKCS5Padding; for a 16-byte block PKCS#5 and PKCS#7 are identical, so that is not a divergence.

"aes-256-cbc"
KEY_BYTES =
32
IV_BYTES =
16
BLOCK_BYTES =
16
OAEP =

RSAES-OAEP with SHA-256 and MGF1-SHA-256 (§10.1), used for both the symmetric key wrap and the KSeF-token payload of §4.5.

All three options have to be stated. OpenSSL::PKey::RSA#public_encrypt cannot express an MGF1 digest at all, and OpenSSL's default MGF1 digest is SHA-1 — so setting rsa_oaep_md alone yields OAEP-SHA256-with-MGF1-SHA1, which is a different scheme that KSeF cannot unwrap. It fails at the far end, not here.

{ rsa_padding_mode: "oaep", rsa_oaep_md: "sha256", rsa_mgf1_md: "sha256" }.freeze
MAX_OAEP_PLAINTEXT_BYTES =

Longest plaintext RSAES-OAEP can carry for a 2048-bit key: k - 2*hLen - 2, so 256 - 64 - 2. Stated because it pins the digest — with SHA-1 the figure would be 214 — and because it is the reason both encrypted payloads here are small ones.

190

Class Method Summary collapse

Class Method Details

.decode(text) ⇒ Object

Lenient base64 for everything it receives, which is the asymmetry that matters: "m0" raises on a line break, and how a server wraps a long value is its business. Nothing is silently accepted as a result — a certificate that decodes to garbage fails in OpenSSL::X509::Certificate.new, with a better message than we would give.



56
# File 'lib/ksef/crypto.rb', line 56

def decode(text) = text.to_s.unpack1("m")

.encode(bytes) ⇒ Object

Strict base64, no line breaks, for everything this gem sends.



50
# File 'lib/ksef/crypto.rb', line 50

def encode(bytes) = [bytes].pack("m0")

.rsa_encrypt(plaintext, public_key) ⇒ String

Returns raw ciphertext, one RSA block wide.

Parameters:

  • public_key (OpenSSL::PKey::RSA)

Returns:

  • (String) —

    raw ciphertext, one RSA block wide



63
# File 'lib/ksef/crypto.rb', line 63

def rsa_encrypt(plaintext, public_key) = public_key.encrypt(plaintext, OAEP)

.sha256(bytes) ⇒ String

Returns the raw 32-byte digest; Digest is the base64-and-size form.

Returns:

  • (String) —

    the raw 32-byte digest; Digest is the base64-and-size form



59
# File 'lib/ksef/crypto.rb', line 59

def sha256(bytes) = OpenSSL::Digest::SHA256.digest(bytes)