Your QKD System isn't Even Classical Secure: Exploiting Vulnerabilities in QKD Post-processing Software
Classicality Overcomes Quantumness
Never Roll Your own Crypto, Including QKD Crypto
― 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/ |
Side-channel Attack
Improper Input Validation
![]() |
| https://xkcd.com/2928/ |
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
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
Post a Comment