Skip to main content

Your QKD System isn't Even Classical Secure: Exploiting Vulnerabilities in QKD Post-processing Software

This work is conducted by the author independently so far. The work so far is not requested, supported, supervised, or endorsed by any third party, nor by any of the departments or employees of any third party. This work does not represent the opinions of any third party, nor any of the departments or employees of any third party. This work is unrelated to the author's employment duties at any third party.  

The author comes from the field of Physics and does not specialize in cybersecurity, if you find any inaccuracies, please contact the author for correction. 

Classicality Overcomes Quantumness

Quantum Key Distribution is often depicted as an unconditional secure solution of key exchange. While this claim is exactly true theoretically, it faces many problems in real implementation. To name a few: blinding attacks, detection efficiency mismatch attacks, photon-number-splitting attacks...  Germany's ministry of cybersecurity have published a technical reports listing all known attacks against QKD systems. However, most of these attacks require the attacker to have abilities to manipulate quantum system. But, what if, the attacker only has a classical computer but still wants to eavesdrop on parties using QKD systems? Fortunately for the attacker and unfortunately for parties using QKD systems, it can actually be done. The attacker can just target at the post-processing software of the QKD system, by which sifting, parameter estimation, error correction and verification, privacy amplification and authentication are done.

Never Roll Your own Crypto, Including QKD Crypto

If the automobile had followed the same development cycle as the computer, a Rolls-Royce would today cost $100, get a million miles per gallon, and explode once a year, killing everyone inside.
― Robert X. Cringely

Writing secure codes is notoriously hard, even big tech companies like Apple, Google and Amazon struggle to do so. When it comes to cryptographic code, it becomes even more challenging. So one can actually image a QKD system provider might not take good care of every aspect of cybersecurity of the QKD post-processing software, leaving vulnerabilities inside. 

To actually examine such possibilities, I decided to audit one QKD post-processing software. An actual attacker can always do reverse-engineering for any QKD systems. However, due to legal concerns, I will only try to look at open source project. (If you can offer me a chance to audit a real commercial QKD solution, I will be very willing to do so!) Filtering out many apparently only used in lab, I found one that might actually used in production available on GitHub.  

Although due to commercial reason they did not release the full and latest code, but we can still audit the legacy available part. And the target has been chosen, let's try to find vulnerabilities inside.

Supply Chain Attack

https://xkcd.com/2347/

This very software under audit is not built from scratch, but has dependencies. For example, it uses 0MQ to send and receive message, Qt for user graphic interface, OpenSSL and so on. 

But since the software have dependencies, that means any vulnerabilities in these dependencies can affect the overall security of the QKD systems. But just like many other projects, these dependencies contain vulnerabilities (here, here and here). If a QKD system fail to keep its software updated or an unknown vulnerability is discovered, it will become a victim of cyberattack. 

And to a lower level, the vulnerabilities can be present in the OS level, firmware level or even hardware level.  So unless a QKD system provider built everything including software and hardware from scratch, they can never guarantee the security of third-party components!

Side-channel Attack

Just like QKD system might work perfectly in theory but still have implementation problem. The same applies to cryptographic program, which always require hardware to run. For example, attacker can steal the victim's private key by collecting electromagnetic leakage from victim's PC , or measuring the time the cryptographic software taken to process the private information

In the technical reports mentioned above, two possible side channels attack are listed. 

One is cache side channel (pp.128), which requires attacker to have the ability to track cache access on the device running the QKD post-processing software, which can be achieved if other certain vulnerabilities exist in the software. 

The other one is power analysis (pp.130), which requires attacker to have the abilitiy to measure the power consumption of the device running the QKD post-processing software, resulting from the fact that the power comsumption of the device depends on the data it processes. However, a vulnerabilities in some CPUs was found, by which the attacker can turn power analysis into timing attack. This is because there is power management in the CPU, when the power input is too high the CPU will slow down its frequency to avoid hitting the power threshold. The consequence is, the difference in power consumption will result in the difference of time needed to process the data. Hence the attacker does not need to actually have access to the the power cable of the QKD systems, the attacker only needs to measure the time the device needed to process the data.

Looking back in the very software I audit here, it use Number Theoretic Transform to do privacy amplification. It makes perfect sense since it is much faster than direct matrix multiplication as definition. However, in this case it is not so nice since the implementation they did is not constant-time. Timing attack against non-constant time NTT has been shown to be practical and alternative constant-time implementation is also available

Improper Input Validation

https://xkcd.com/2928/

When a software allows external input, espically when the external input could be remote, carefully validation must be done. Because you cannot stop the attacker to input the craziest data. For example, SQL injection works when the software failed to validate that the input format, resulting in the SQL database process the input as command . And the latest trend is to do prompt injection to AI based on large language model. 

We now examine how the software I am auditing processes the message it receives from the other party.

If you want to get the main idea without going into the details of the code, you can just skip the following code blocks and go to the end of this section.

For example, this is sifting module, you can see recv() is called to receive message and operater >> is used to extract the data.

bin/modules/qkd-sifting-bb84.cpp
bool qkd_sifting_bb84::process_alice(qkd::key::key & cKey,
        qkd::crypto::crypto_context & cIncomingContext,
        qkd::crypto::crypto_context & cOutgoingContext) {
    /* Some code omitted here*/
    cMessage = qkd::module::message();
    try {
        if (!recv(cKey.id(), cMessage, cIncomingContext)) return false;
    }
    catch (std::runtime_error const & cRuntimeError) {
        qkd::utility::syslog::crit() << __FILENAME__ << '@' << __LINE__ << ": "
                << "failed to receive message: " << cRuntimeError.what();
        return false;
    }
    qkd::utility::memory cBasesPeer;
    cMessage.data() >> cBasesPeer;
/* Some code omitted here*/
}

 If you trace into how recv() and operater >> are implemented, you will find,

include/qkd/utility/message.h

class message {

 /* Some code omitted here*/

public:


/* Some code omitted here*/

    inline qkd::utility::buffer & data() { return m_cData; }

/* Some code omitted here*/

    inline qkd::utility::buffer const & data() const { return m_cData; }

/* Some code omitted here*/


private:

/* Some code omitted here*/

    qkd::utility::buffer m_cData;

/* Some code omitted here*/

}


include/qkd/utility/memory.h

inline uint64_t size() const {

return m_nSize;

}


include/qkd/utility/memory

inline buffer & operator>>(buffer & lhs, memory & rhs) {

    lhs.pop(rhs);

    return lhs;

}


inline void pop(uint64_t & i) {

        uint64_t x;

        pick(&x, sizeof(x));

        i = be64toh(x);

    }


inline void pop(qkd::utility::memory & m) {

        uint64_t nSize; pop(nSize);

        m.resize(nSize);

        pick(m.get(), nSize);

    }


inline void pick(void * cData, uint64_t nSize) {

        if ((m_nPosition + nSize) > size()) throw std::out_of_range("buffer pick out-of-range");

        memcpy(cData, get() + m_nPosition, nSize);

        m_nPosition += nSize;

    }

You can see the operator >> basically first read in a memory block that indicates how large the data block is, and then just read the indicated size of following memory. A malicious message could just put in an invalid size indication here causing a buffer overread. Indeed, there is a boundary check here, but it does not help much since the threshold m_nSize can also be invalid in a maliciously constructed message! 

What is worse is how the authentication module works, 

lib/module/module.cpp

bool module::recv(qkd::key::key_id nKeyId,

                  qkd::module::message & cMessage,

                  qkd::crypto::crypto_context & cAuthContext,

                  qkd::module::message_type eType) {

    

    qkd::module::connection * cCon = nullptr;

   /* Some code omitted here*/

    

    if (!cCon->recv_message(cMessage)) {

        return false;

    }

   /* Some code omitted here*/

    

 /* Some code omitted here*/

        cAuthContext << cMessage.data();

        cMessage.data().set_position(0);

        return true;

    /* Some code omitted here*/


lib/crypto/evhash.cpp

void update(qkd::utility::memory const & cMemory) {

        char * data = (char *)cMemory.get();

        int64_t nLeft = cMemory.size();

        

        if (m_nRemainderBytes + nLeft < block_size()) {

            memcpy(m_cRemainder + m_nRemainderBytes, cMemory.get(), nLeft);

            m_nRemainderBytes += nLeft;

            return;

        }

        

        if (m_nRemainderBytes) {

            /* Some code omitted here*/

        }

    }


The authentication module depends on m_nSize to determine the length of the message goes into the transcript while it does not directly affect the module doing other jobs such as sifting (it affects the boundary check within pick(), but one theoretically can bypass it with integer overflow), or the attacker just stops some ping() message going through and reacts as if the other party reacts (so the ping message goes into the transcript in one party but not the other party). This means the attacker can take advantage of this to escape being caught alternating the message communicated between the parties.

For those does not have time to go through all the code, here is an oversimplified explanation: 





Taken from my slides for my QIP 2026 Rump Session Talk. Notice that the structure of the message and the corresponding processing procedure have been oversimplified since the target audience were mainly working in the field of Physics.

To be fair, such attack does not seem as easy as in the oversimplified version. The buffer overread itself does not give the attacker private information like Heartbleed. And the attack targeting authentication module is not so easy either given the available code. Since the message still have to satisfy certain structure to pass certain input validation to make the module work without exiting, and causing integer overflow in uint64_t is extremely unlikely. And to change the transript by stopping ping message highly depends on the pattern of doing ping in the complete software. And another layer of authentication might be able to detect the attacker. But the presence of such a vulnerablity is still concerning since it shows that the attention paid to code reviewing and testing is not enough and a seasoned cybersecurity researcher might find out more vulnerablities.


Disclosure Process

I had contacted the owner of the Git repository when I first discovered the vulnerabilities in November 2025, and got reply immediately. Unfortunately, the author does not work for the QKD solution provider any more and can only offer me contact information of the contact personals. I contacted them the very next day, and still waiting for a reply from them. I have also asked third-party organizations for help with communication but no luck. Optimistically, maybe they have already fixed the vulnerabilities or changed the code so that the vulnerabilities are not relevant any more. 

Conclusion

Just as the title goes, if you write the code for QKD systems without paying enough attention, it will enable even classical attackers to attack your QKD systems. So please pay attention to your code writing!


Comments