efrisapi.com

Docs / Protocol

The EFRIS protocol

What a request actually looks like on the wire, and the handful of details either absent from URA's specification or contradicted by the live service. These are the reasons a first integration fails before it ever submits a document.

The envelope

Every request is the same three-part structure. Only data.content changes between interfaces.

{
  "data": {
    "content":   "...",        // the interface's request, encoded
    "signature": "...",        // RSA/SHA1 over the ENCODED content
    "dataDescription": { "codeType": "0", "encryptCode": "2", "zipCode": "0" }
  },
  "globalInfo": { "appId": "AP04", "interfaceCode": "T109", "tin": "...", ... },
  "returnStateInfo": { "returnCode": "", "returnMessage": "" }
}

encryptCode decides the encoding

Valuecontent holdsSignature over
2base64( AES-128-ECB( json ) )that base64 ciphertext
1base64( json )that base64 string
0the JSON object itself, embeddednot signed

The signature covers the transmitted value, not the plaintext. For an encrypted request that means signing the base64 ciphertext. Signing the original JSON produces a well-formed request that URA rejects.

Cryptography

The T104 handshake

T101 (server time) and T104 (key exchange) precede the session key, so they are sent unsigned and unencrypted. T104 returns the AES session key, wrapped:

  1. Base64-decode data.content to get a small JSON object.
  2. Read passowrdDes from it — URA's spelling. Reading passwordDes raises a KeyError against a live server.
  3. Base64-decode that value, then RSA-decrypt it with your private key.
  4. The result is another base64 string. Decode it again to reach the 16 raw key bytes.

If your key is not exactly 16 bytes, you stopped one step early. The RSA output is base64 text, not the key. Skipping the second decode leaves a 24-character value that looks plausible and fails every later call on padding.

inner = json.loads(base64.b64decode(envelope["data"]["content"]))
wrapped = base64.b64decode(inner["passowrdDes"])          # URA's spelling
intermediate = private_key.decrypt(wrapped, padding.PKCS1v15())
aes_key = base64.b64decode(intermediate)                  # decode TWICE
assert len(aes_key) == 16

Responses

A response may be gzipped before encryption. Content beginning H4sI is a base64 gzip stream, and what is inside it may still be AES encrypted — decompress first, then attempt decryption, then parse.

Where the specification is wrong

Next


The EFRIS API Kit handles this case already — it is one of the rejections the library was calibrated against. This page stays free either way.