Photonic Logo
Projects
Blog
About
Résumé
Projects
Blog
About
Résumé

OPAQUE-ly Securing Logins with a Modern aPAKE Protocol

17 May 2026

Contents
  • A Look Back On Secure Remote Password (SRP)
  • Triple Diffie-Hellman (3DH)
  • …Don’t We Want Passwords?
  • Oblivious Pseudorandom Functions (OPRFs)
  • OPAQUE
  • Client Requests Login (Key Exchange Message 1)
  • Server Responds (Key Exchange Message 2)
  • Client Proves Itself (Key Exchange Message 3)
  • Server Finalizes
  • The Modularity of OPAQUE and Post-Quantum Cryptography
  • Disclosure
  • Image Credits
  • Footnotes

In a previous blog post, we took a look at several key exchange protocols, including the classic Diffie-Hellman Key Exchange protocol, the station-to-station protocol, and Secure Remote Password. These protocols were generally tried and tested across decades, but their age also means that improvements accrued across the decades are not included in these legacy protocols. Thus the need for a modern, Augmented Password-Authenticated Key Exchange (aPAKE) protocol was born. One of these state-of-the-art aPAKE protocols is OPAQUE, first introduced in 2018 and formally specified in Request for Comments (RFC) 9807 in 2025. How does it work? Let’s find out!

A Look Back On Secure Remote Password (SRP)

Let’s recap on what SRP is trying to accomplish. Two friends, Alice and Bob, are talking to each other. Alice wants to send a message to Bob, but doesn’t want anyone else from reading it. Thus they would need to have a common key (a symmetric key) for encrypting and decrypting data. Alice would also want to verify that she is indeed talking to Bob and not someone else.

These two desiderata were achieved by the SRP protocol, which was first introduced in 1997. However, even the latest version, SRP-6a (as defined by RFC5054 in 2007), is not perfect.

  • “Security Through Legacy”: In short, we assume that SRP is secure because we haven’t found anything wrong with it. SRP was developed in a time before Universal Composability became the norm, and so we haven’t truly analysed it under adversarial network conditions.
  • Vulnerability to Server Compromise: Recall in SRP that a verifier value v=gxmod  pv = g^x \mod pv=gxmodp is stored on the server. In effect, this verifier value still acts as a ‘password’ of sorts; an attacker that has precomputed all possible values of gxmod  pg^x \mod pgxmodp can still recover the secret key xxx.
  • Flexibility: SRP-6a is heavily tied to modular operations on integers and cannot harness modern developments such as elliptic curve technologies. In fact, if the Discrete Logarithm Problem (explained further below) is solved by a quantum computer, all Diffie-Hellman-related protocols (including SRP) would be trivially broken and no longer secure.

What we want for a modern aPAKE protocol is to address all of these concerns — where the protocol’s security has been rigorously examined and vetted, is resistant to attacks in case of a server compromise, and grants greater flexibility in its underlying operations in case a pivot to post-quantum cryptographic techniques is warranted.

Triple Diffie-Hellman (3DH)

The first thing that needs replacing within SRP is the key exchange mechanism. As previously mentioned, the SRP key exchange mechanism is derived from Diffie-Hellman Key Exchange (DHKE). The fundamental principle that DHKE relied on is the observation that

(ga)b=gab=gba=(gb)a(g^a)^b = g^{ab} = g^{ba} = (g^b)^a (ga)b=gab=gba=(gb)a

for certain structures, called groups. Traditional DHKE uses the multiplicative group of integers modulo ppp as the underlying group, where ppp is a prime, but the principle of DHKE extends beyond just numbers. All we need is a cyclic group, which is a group where all the elements are simply ‘exponents’1 of (at least) one element ggg, called a generator. To put it more formally, for any cyclic group GGG, there is an element ggg such that for any element in GGG, say xxx, we are able to find a corresponding integer nnn such that x=gnx = g^nx=gn. Crucially, trying to reverse this process, i.e. to find nnn given the group and xxx, is difficult. This is called the Discrete Logarithm Problem2.

Now let us recall how DHKE works. Suppose Alice and Bob wants to derive a shared key. They first agree, publicly, on a cyclic group to use and a generator ggg.

  1. Alice randomly chooses a positive integer aaa. Bob does the same, choosing bbb.
  2. Alice computes A=gaA = g^aA=ga and sends it to Bob over the public channel. Bob computes B=gbB = g^bB=gb and sends it to Alice.
  3. Alice computes s=Bas = B^as=Ba and Bob computes s=Abs = A^bs=Ab.

Diffie-Hellman Key Exchange, Illustrated
Diffie-Hellman Key Exchange, Illustrated

Observe that both Alice and Bob derive the same secret key sss at the end of the DHKE protocol. They can then use sss to encrypt messages between each other going forward. This is almost perfect, if not for the fact that a malicious actor (say, Mallory) could perform a man-in-the-middle (MiTM) attack and read the messages.

In this case, Mallory simply intercepts the key exchange messages of Alice and Bob and sets up two encrypted channels: one between Alice and Mallory and one between Mallory and Bob. To Alice and Bob, it would seem as though they are still communicating over an encrypted channel, but in reality their messages are being read by a third party.

How a Man-in-the-Middle Attack in DHKE Could Occur
How a Man-in-the-Middle Attack in DHKE Could Occur

There are several solutions to this problem, such as the Station-to-Station protocol described in the previous blog post. Here we will use another solution — triple Diffie-Hellman (3DH). It is almost the same as regular DHKE, except that we introduce public keys into the mix: Alice has a private key aaa and a public key A=gaA = g^aA=ga, and Bob has a private key bbb with public key B=gbB = g^bB=gb. Let’s ignore how Alice and Bob share their public keys with each other beforehand3; how does the 3DH key exchange work?

  1. Alice and Bob choose positive integers xxx and yyy respectively, called the secret keyshares.
  2. Alice computes X=gxX=g^xX=gx and sends it to Bob over the public channel. Bob computes Y=gyY = g^yY=gy and sends it to Alice. The values XXX and YYY are called the public keyshares.
  3. Alice and Bob then compute the ‘shared values’ as follows.
    • The first shared value s1s_1s1​ is akin to the DHKE shared value. Alice computes YxY^xYx and Bob computes XyX^yXy.
    • The second shared value s2s_2s2​ checks if Bob can successfully prove that he is, indeed, Bob. While Alice computes BxB^xBx using Bob’s public key, Bob has to use his private key bbb to compute XbX^bXb. That way Bx=(gb)x=gbx=(gx)b=XbB^x = (g^b)^x = g^{bx} = (g^x)^b = X^bBx=(gb)x=gbx=(gx)b=Xb.
    • The third shared value s3s_3s3​ is similar to the second, but now it’s Alice who needs to prove who she is. Alice computes YaY^aYa while Bob computes AyA^yAy; both sides are the same because Ya=(gy)a=gay=(ga)y=AyY^a = (g^y)^a = g^{ay} = (g^a)^y = A^yYa=(gy)a=gay=(ga)y=Ay.
  4. With all the shared values obtained, a Key Derivation Function (KDF) is used to ‘combine’ s1s_1s1​, s2s_2s2​, and s3s_3s3​ alongside all public values XXX, YYY, AAA, and BBB into a single shared key4, say SSS.

3DH Key Exchange, Illustrated
3DH Key Exchange, Illustrated

Let’s look at a simple demonstration of a 3DH key exchange in Python. Here we use the multiplicative group of integers modulo 23 (i.e. p=23p = 23p=23) with the generator being g=5g = 5g=5. Note that a function call like pow(a, b, m) means abmod  ma^b \mod mabmodm, i.e. raise aaa to the power of bbb modulo mmm.

In the end you can see that the dh1, dh2, and dh3 values are the same for Alice (on the left) and Bob (on the right). Note that 3DH successfully prevents a MiTM attack since Mallory can’t recover the private key of either Alice or Bob and impersonate them. In fact, 3DH is a bilaterally authenticated key exchange mechanism, allowing both sides to verify their identities and derive a common secret for encryption.

…Don’t We Want Passwords?

Obviously, 3DH doesn’t work with passwords. But what we want is a Password-Authenticated Key Exchange protocol! It’s fine for the server to keep using a private/public key construction, but how do we get the client to generate the private and public keys to use in 3DH from a password?

A fairly obvious approach would be just to take the user’s password, pass it through a KDF5 to generate the client’s private key ccc, and generate the public key C=pcC = p^cC=pc. We then send the public key to the server for use in 3DH. What’s wrong with this? Well, since the client public key is (by definition) public, anyone can see what C=gcC = g^cC=gc is. A malicious attacker could record down the value of CCC, precompute all possible public keys, and then seeing which password matches. This rainbow table attack means that anyone who obtains the public key of the client (which is everyone) could try and crack the client’s password without first breaching the server.

What we want is to somehow make this key derivation process rely on a secret value that the server (and only the server) has. But the server can’t send this value to the client (as the channel is still not secure and thus could leak this secret value) and the client can’t send its password to the server for processing (as that’ll just leak the client’s password). We need a way to protect the client’s password, send it to the server to include its secret value, before sending it back to the client for use in a KDF.

What could we use to achieve this?

Oblivious Pseudorandom Functions (OPRFs)

Introducing OPRFs! These functions help us solve the problem of trying to mix a server secret into the password-based key derivation process.

Before we look into the “oblivious” part of OPRFs, what is a Pseudorandom Function (PRF)? Essentially, a PRF looks like a function that generates random outputs, except that the same input generates the same output. Except, it is also fed a secret key (or seed) so that the outputs of the PRF are the same for the same inputs.

To put it more precisely,

A Pseudorandom Function (PRF) is a function FFF taking a seed sss and input xxx such that, for any sss chosen from a uniformly random distribution, the function g(x)=F(s,x)g(x) = F(s,x)g(x)=F(s,x) is indistinguishable from a function sampling uniformly from the image of FFF.

In practice, encryption functions such as AES could be used as PRFs: just define F(s,x)=AESs(x)F(s,x) = \texttt{AES}_s(x)F(s,x)=AESs​(x) (where sss is the AES key) and you’ve got a PRF. However, that’s not how we are going to create an Oblivious PRF. For an OPRF, the seed is held by one party (e.g. the server) while the input is held by another (e.g., the client). So we can’t use the AES trick to compute the OPRF output as that’ll involve one party needing both the seed and the input. We need to do something cleverer.

Let’s go back to our good friend: the cyclic groups. Due to the Discrete Logarithm Problem, we know that finding nnn given gng^ngn is hard. Assuming that we choose nnn uniformly randomly, then gng^ngn would also be uniformly random. Also, if we make the size of the cyclic group a prime, we can utilise the following interesting fact6:

Let Zp\mathbb{Z}_pZp​ denote the set of integers {0,1,2,3,…,p−1}\{0, 1, 2, 3, \dots, p - 1\}{0,1,2,3,…,p−1}. Then for any non-zero n∈Zpn \in \mathbb{Z}_pn∈Zp​ there exists k∈Zpk \in \mathbb{Z}_pk∈Zp​, known as the multiplicative inverse of nnn, such that nk=1mod  pnk = 1 \mod pnk=1modp. We may abuse notation and write n−1n^{-1}n−1 to denote the multiplicative inverse of nnn.

This insight allows us to conceptually create an OPRF with seed sss and input xxx within a cyclic group of prime size as follows:

  1. Convert xxx into a group element XXX.
  2. Randomly take a non-zero b∈Zpb \in \mathbb{Z}_pb∈Zp​, called the blind.
  3. Generate the blinded element B=XbB = X^bB=Xb and send it to the server.
  4. On the server, assuming sss is an integer, compute Z=BsZ = B^sZ=Bs and send it back to the client. Note that Z=(Xb)s=XbsZ = (X^b)^s = X^{bs}Z=(Xb)s=Xbs.
  5. Unblind the blinded element by finding b−1b^{-1}b−1 and computing Y=Z(b−1)Y = Z^{(b^{-1})}Y=Z(b−1). Note that Y=Xbs(b−1)=XsY = X^{bs(b^{-1})} = X^sY=Xbs(b−1)=Xs. This YYY can then be converted into a more ‘suitable’ form (e.g., by converting YYY into bytes) for further processing.

How an OPRF works
How an OPRF works

Let’s see an OPRF in action using the group of integers under addition modulo 23. In such a group the standard notation like XbX^bXb means b×Xb \times Xb×X (i.e., XXX multiplied by bbb). Let’s again use Python for demonstration; note that

  • the expression randint(1, p-1) means to generate a random integer from 1 to p−1p-1p−1 inclusive;
  • an expression like (b * x) % p means (b×x)mod  p(b \times x) \mod p(b×x)modp; and
  • the expression pow(b, -1, p) is to find b−1mod  pb^{-1} \mod pb−1modp, i.e. the multiplicative inverse of bbb modulo ppp.

In the end you can see that the client’s OPRF output is the same as someone who has both the OPRF input and seed.

Also observe that, throughout this process, the server never sees the input xxx (or XXX) and the client never sees the seed sss. Thus this is, in theory, an OPRF. In fact, this is largely how RFC9497 defines the OPRF on cyclic groups with a prime size (like the group of integers under addition modulo 23). Of course, there are some nuances to make this an actual, standard worthy OPRF, which are covered in the aforementioned RFC.

OPAQUE

Now that we understand how the 3DH key exchange mechanism works and how an OPRF could be used to include a server secret into a key derivation operation, we can finally take a look at the OPAQUE protocol. Essentially, it is an aPAKE that uses an OPRF, hence the name OPAQUE7.

Remember how 3DH almost worked except for the part of generating client private/public keys? Now, with an OPRF, we can solve this problem! The core idea is simple:

  1. Client converts password into a key, say xxx, using a KDF.
  2. Use the OPRF to incorporate the server’s OPRF seed alongside xxx to generate the new key x′x'x′.
  3. Use x′x'x′ to generate the client’s private key ccc and public key C=gcC = g^cC=gc.
  4. Perform 3DH key exchange, bilaterally authenticating each party.
  5. Use derived shared key to encrypt communications.

This is almost everything that is in OPAQUE, except that we still have to solve two problems:

  • How does the server get the client’s public key?
  • How does the client get the server’s public key?

The first problem is solvable if we send the client’s public key during registration8, but the second isn’t so simple. Remember, the throughout the entire authentication process, the client and server are talking in an insecure channel. That means that if the server sends its public key to the client in the clear, a malicious actor could swap out that public key with their own. The solution is to use a masking key that allows the client and server to encrypt the server public key. This masking key is based off the OPRF output and is sent to the server during registration alongside the client’s public key.

Let’s walk through how the OPAQUE login flow works in its entirety. Suppose our Carol is the client and our Steve is the server. They agree to use a cyclic group of prime size9 with generator ggg and to use the 3DH key exchange mechanism. This means that they are using the OPAQUE-3DH protocol. How does Carol log in?

Client Requests Login (Key Exchange Message 1)

Carol first passes her password into a special cryptographic hash function that converts her password into a group element, say PPP. We now generate the OPRF blind bbb and get B=PbB = P^bB=Pb as our blinded element10. Then a cryptographic nonce for Carol, ncn_cnc​, as well as the 3DH keyshares xxx and X=gxX = g^xX=gx are generated. We’ll see what ncn_cnc​ is used for in a while, but for now just understand that it is a number that should never be used more than once (i.e., it should not be repeated in another login attempt).

Carol sends the blinded password BBB, her nonce ncn_cnc​, and her public keyshare XXX to Steve in a structure called KE1 (Key Exchange 1). Now because Steve also needs to correctly retrieve Carol’s public key CCC and masking key from his own records, thus Carol also needs to send her username to Steve alongside KE1.

Server Responds (Key Exchange Message 2)

Steve receives Carol’s KE1 message and her username. He uses her username to retrieve her registration record, which includes Carol’s public key CCC, masking key, and an envelope containing a nonce nen_ene​ and a Message Authentication Code (MAC) (or authentication tag) mem_eme​. What’s this MAC used for? Well, it is to ensure that Carol’s public key CCC, masking key, and the envelope itself has not changed during storage. Later, this MAC will also be used to check whether Carol provided the correct password, but more on that in a bit.

Like with Carol, Steve also generates a random nonce nsn_sns​ alongside his 3DH keyshares yyy and Y=gyY = g^yY=gy. Now Steve already has his own private key sss and public key S=psS = p^sS=ps, so he can already complete the 3DH key exchange on his end. He computes dh1=Xy\texttt{dh}_1 = X^ydh1​=Xy, dh2=Xs\texttt{dh}_2 = X^sdh2​=Xs, and dh3=Cy\texttt{dh}_3 = C^ydh3​=Cy, using them to generate the session key KKK as well as two MACs: a server MAC msm_sms​ and an expected client MAC mc′m_c'mc′​.

But Carol still needs the output of the OPRF to complete her side of the key exchange! Steve has the OPRF seed, but has not applied it to the blinded password yet. He thus transforms that seed into a key using a KDF and then uses it to evaluate the blinded password BBB, obtaining Z=BOPRFkeyZ = B^{\texttt{OPRFkey}}Z=BOPRFkey.

Steve is almost ready to send these values back to Carol, but he also needs to send his public key SSS to Carol. But remember how that the channel is still insecure? Thus, Steve needs to use the masking key stored in the registration record of Carol and a masking nonce nmn_mnm​ to encrypt11 of SSS as well as the envelope. Providing the public key SSS and the envelope to Carol allows her to check if (a) her password is correct and (b) whether Steve has the correct envelope (or if someone is pretending to be Steve).

So, at the end of all that, Steve bundles the evaluated blind ZZZ, the masking nonce nmn_mnm​, and the encrypted SSS and envelope into a CredentialResponse structure, and packs the server nonce nsn_sns​, his public keyshare YYY, and server MAC msm_sms​ into a AuthResponse structure. These two structures are bundled together and sent to Carol as the KE2 message.

Client Proves Itself (Key Exchange Message 3)

Now, back to Carol. She receives the KE2 message from Steve and unpacks it.

The first thing she does is to take the evaluated blind ZZZ and unblind it. As previously discussed, this is done by finding the multiplicative inverse of the blind (i.e., b−1b^{-1}b−1) and then computing Z(b−1)Z^{(b^{-1})}Z(b−1) , since

Z(b−1)=(BOPRFkey)(b−1)=BOPRFkey×(b−1)=(Pb)OPRFkey×(b−1)=Pb×OPRFkey×(b−1)=POPRFkey.\begin{align*} Z^{(b^{-1})} &= \left(B^{\texttt{OPRFkey}}\right)^{(b^{-1})}\\ &= B^{\texttt{OPRFkey} \times (b^{-1})}\\ &= \left(P^b\right)^{\texttt{OPRFkey} \times (b^{-1})}\\ &= P^{b \times \texttt{OPRFkey} \times (b^{-1})}\\ &= P^{\texttt{OPRFkey}}. \end{align*} Z(b−1)​=(BOPRFkey)(b−1)=BOPRFkey×(b−1)=(Pb)OPRFkey×(b−1)=Pb×OPRFkey×(b−1)=POPRFkey.​

The OPRF output, POPRFkeyP^{\texttt{OPRFkey}}POPRFkey, is used as input to a KDF to generate a random password. This random password is the aforementioned output of the OPRF that was used to create the masking key, which Carol can now obtain. So she can use that key to recover Steve’s public key SSS as well as the envelope from the CredentialResponse in the KE2 message. Typically, now Carol would check if SSS, along with the envelope, produces the same envelope MAC mem_eme​ as what was found in the envelope itself. If not, it might be the case that Carol got her password wrong.

Assuming it is correct, this random password is also used to (re)generate the client private key ccc and client public key C=gcC = g^cC=gc. She can now complete the 3DH key exchange on her end by computing dh1=Yx\texttt{dh}_1 = Y^xdh1​=Yx, dh2=Sx\texttt{dh}_2 = S^xdh2​=Sx, and dh3=Yc\texttt{dh}_3 = Y^cdh3​=Yc, using them to generate the session key KKK, the expected server MAC ms′m_s'ms′​, and the client MAC mcm_cmc​. Carol checks Steve’s server MAC msm_sms​ against her own ms′m_s'ms′​ and verifies that they are the same. If it is, she sends Steve her client MAC mcm_cmc​ as the KE3 message.

At this point, Carol has completed authenticating Steve and is ready to begin encrypted communications using the session key KKK. However, Steve still needs to do one more thing.

Server Finalizes

Steve receives Carol’s client MAC mcm_cmc​ and compares it with his expected MAC mc′m_c'mc′​. If they do not match, he drops the connection. Otherwise, he would have verified Carol’s identity and can begin encrypted communications using the same session key KKK.

Phew! That was a lot. As you can see, the OPAQUE protocol is quite dense. But understanding the two main aspects of OPAQUE, namely the key exchange mechanism and the inclusion of server secrets into the password-based key derivation process, it becomes more manageable and understandable as a whole.

The Modularity of OPAQUE and Post-Quantum Cryptography

An obvious surface-level improvement of OPAQUE is that it takes less round trips than SRP, as seen in the diagram below.

SRP vs OPAQUE Round Trips
SRP vs OPAQUE Round Trips

Another improvement that OPAQUE has over SRP is that it is very modular. OPAQUE is made up of two main components: the key exchange mechanism and the OPRF. In RFC9807, they specified 3DH as the key exchange mechanism and the cyclic group-based OPRF as the OPRF implementation. But these can be swapped out for others!

An exciting opportunity is to implement a post-quantum secure OPAQUE protocol by swapping out the key exchange mechanism and the OPRF with ones that are post-quantum secure. So far, the National Institute of Standards and Technology (NIST) have standardized several post-quantum cryptographic protocols, including a module lattice-based key encapsulation mechanism in FIPS203 which, when paired with the corresponding digital signature algorithm in FIPS204, could replace 3DH as a post-quantum secure key exchange mechanism. A post-quantum secure OPRF remains elusive, though. But the modularity of OPAQUE means that, when a post-quantum OPRF become provably secure, they can be swapped with the existing OPRF to make a fully post-quantum secure aPAKE protocol built upon OPAQUE.

Disclosure

No artificial intelligence technologies or tools were used in the creation of this blog post.

Image Credits

Photo by Compare Fibre on Unsplash. Resized from the original.

Footnotes

  1. To be a little clearer, ‘exponentiation’ by nnn means that we apply the operation of a group on an element nnn times. In the case of the multiplicative group of integers modulo ppp, that is the same as raising the power of an integer to nnn before taking the remainder modulo ppp. ↩

  2. It is actually not known how difficult it is to recover nnn; we only have empirical experiments showing that it is hard. ↩

  3. A typical approach may be to rely on Public Key Infrastructure (PKI) with the use of Certificate Authorities (CAs). In OPAQUE, however, we’ll do something cleverer, as we will see. ↩

  4. There are many types of KDFs, each with different properties and uses. In this description of 3DH the KDF can just be thought of as combining these inputs into a single, fixed-length key. ↩

  5. This naive approach is incredibly insecure. A cryptographic salt is typically included as another input to the KDF to prevent trivial brute force attacks. In this hypothetical scenario, however, this doesn’t matter. ↩

  6. This fact is due to Zp\mathbb{Z}_pZp​ being a field, in which all non-zero elements in it must have a multiplicative inverse. Specifically, Zp\mathbb{Z}_pZp​ is the Galois Field of order ppp. ↩

  7. To quote RFC9807: “The name “OPAQUE” is a homonym of O-PAKE, where O is for Oblivious. The name “OPAKE” was taken.” ↩

  8. Like with most PAKEs, the registration phase of the protocol is assumed to be performed in some secure channel. So sending the masking key during the registration phase is okay, but sending it during the login phase is not. ↩

  9. In RFC9807 they use an Elliptic Curve Group such as Ristretto255, which is a cyclic group of prime size. However, introducing elliptic curves here would just muddy the explanation. ↩

  10. In RFC9807 the elliptic curve groups are additive groups, meaning that the fundamental group operation is expressed as g+hg + hg+h instead of ghghgh, and consequently the ‘exponentiation’ of group elements is written as ngngng instead of gng^ngn as we have been doing here. For consistency of notation within this blog post, however, we’ll stick to using ‘exponentiation’. ↩

  11. Although the word “encrypt” might make it seem like this is a computationally expensive process, what is actually used is a one-time pad based on Carol’s masking key and the masking nonce. In particular, the masking key and masking nonce is expanded using a KDF to the length of the message, and then used to XOR the message. ↩

Suggest an editLicensingCite this post
PreviousDesigning a Zero-Trust, Secure File Manager

Citing This Post

APA7

Kan, O. K. (2026, May 17). OPAQUE-ly Securing Logins with a Modern aPAKE Protocol. Photonic. Retrieved May 17, 2026, from https://photonic.dev/blog/2026-05-17/opaque-ly-securing-logins-with-a-modern-apake-protocol/

BibTeX

@misc{Photonic_2026,  title={OPAQUE-ly Securing Logins with a Modern aPAKE Protocol},  url={https://photonic.dev/blog/2026-05-17/opaque-ly-securing-logins-with-a-modern-apake-protocol/},  journal={Photonic},  author={Kan, Onn Kit},  year={2026},  month={May},  day={17}}
  • GitHub Logo
  • Bandcamp Logo
  • LinkedIn Logo
Copyright © 2026 PhotonicGluon Version 1.3.1
Source Code Credits Licensing
Printed on TIMESTAMP