Home › Security › Authentication & authorization
algorithm medium

AKA (authentication and key agreement)

Problem A network operator must let millions of devices with a tamper-resistant chip join networks run by others, proving who each device is and that the network is genuine, without sharing the chip's secret with those networks.

Symmetric-key challenge-response in which a chip and its home server prove knowledge of one key, with a sequence number for freshness, and derive session keys.

card: 3 to 5 keyed-function calls per challenge (Milenage: 5 AES-128 blocks); serving side: 1 comparison; toy functions 8.5 ns per card check measured
akamilenagetuakeap-akasimmutual-authenticationsequence-number3gpp
Overview

AKA lets a tamper-resistant chip and a network prove themselves to each other with one symmetric key that only the chip and its home server hold. The home server hands the visited network a one-time "vector": a random challenge, the expected answer and fresh session keys. The chip checks a MAC and a sequence number inside the challenge, so fake networks and replays fail. Used by cellular networks, Wi-Fi access over EAP and private networks.

Key facts

Use when

  • A chip and a backend share one symmetric key and the device must verify the backend too: cellular (2G to 5G), Wi-Fi roaming over EAP, private networks, constrained devices.
  • Visited operators must authenticate users without holding their keys.

Avoid when

  • Devices cannot hold a protected key: use passkeys or mutual TLS.
  • Stateless servers: SQN is per-subscriber state.

Deep dive

How it works

Parts:

  • K — long-term key, only in the card (SIM or secure element) and the home auth centre (HE/AuC).
  • OPc — operator constant mixed into the f-functions (Milenage: OPc = OP xor E_K(OP)).
  • f1, f1* — MACs; f2 gives RES; f3, f4 give CK, IK; f5, f5* give the anonymity key AK.
  • SQN — 48-bit counter: home keeps SQN_HE; the card keeps SQN_MS (highest accepted) and an array of recent entries.
  • Vector — RAND, XRES, CK, IK, AUTN from home, used once by the serving network; AUTN = (SQN xor AK) ‖ AMF ‖ MAC, MAC = f1(SQN ‖ RAND ‖ AMF).

Steps (TS 33.102 clause 6.3):

  1. Home draws RAND (CSPRNG) and the next SQN, computes MAC, XRES, CK, IK, AK, builds AUTN, and sends a batch of vectors to the serving network.
  2. Serving network sends RAND ‖ AUTN of one unused vector to the card.
  3. Card computes AK = f5(RAND), unmasks SQN, computes XMAC = f1(SQN ‖ RAND ‖ AMF). XMAC differs from MAC: network not genuine, abandon.
  4. Card checks SQN against its accepted range. Stale: sync failure with AUTS = (SQN_MS xor f5*(RAND)) ‖ f1*(SQN_MS ‖ RAND ‖ 0).
  5. Otherwise card returns RES = f2(RAND) and derives CK and IK.
  6. Serving network compares RES with XRES; equal means the card holds K. CK and IK then protect the link.
  7. After a sync failure the serving network sends RAND and AUTS to home; home recovers SQN_MS, verifies MAC-S, resets its counter, sends fresh vectors (clause 6.3.5).
 Card (K, OPc, SQN_MS)     Serving network        Home (K, OPc, SQN_HE)
                                   |-- auth data request ---->|
                                   |<-- AV(1..n) -------------|  RAND,XRES,CK,IK,AUTN
   |<-- RAND ‖ AUTN ---------------|
   | f5 -> AK -> SQN; f1 -> XMAC == MAC ?; SQN fresh ?
   |-- RES ------------------------>  RES == XRES ?
   |-- AUTS (if stale) ----------->|-- RAND, AUTS ----------->|  reset SQN_HE

Freshness. Annex C (informative) splits SQN = SEQ ‖ IND. The card keeps SEQ_MS(i) for each index i of an array. It accepts SEQ if SEQ > SEQ_MS(IND) and SEQ is at most Δ above its highest; otherwise it reports its highest SQN. Different indices tolerate out-of-order use.

Algorithm sets. Milenage (TS 35.206) is built on AES-128. TUAK (TS 35.231) uses the Keccak-f[1600] permutation, with 128 or 256-bit K and RES of 32 to 256 bits.

Newer generations. In 5G AKA (TS 33.501 clause 6.1.3.2) the home returns XRES*; the AUSF keeps it and gives the serving network only HXRES*, the 128 least significant bits of SHA-256(RAND ‖ XRES*). The serving network checks HRES* against HXRES*, then forwards RES* so the home network confirms the result. EAP-AKA' (RFC 9048, which updates RFC 4187 and RFC 5448) runs AKA inside EAP with keys bound to the access network name. 5G sends the identity as a concealed SUCI (ECIES profile A with X25519, or B with secp256r1).

Code

Rust, std only. The flow is real; the f-functions are a toy keyed mix (not Milenage, not secure) behind a function type, so Milenage or TUAK can be plugged in. The card uses an Annex C style array (size 4).

use std::{hint::black_box, time::Instant};
// (K, OPc, which f, inputs) -> 64 bits; plug in Milenage or TUAK here
type F = fn(&[u64; 2], u64, u8, [u64; 3]) -> u64;
const F1: u8 = 1; const F2: u8 = 2; const F5: u8 = 5; const F1S: u8 = 6; const F5S: u8 = 7; // f3, f4 (CK, IK) work like f2
const M48: u64 = (1 << 48) - 1;
// toy keyed mix: NOT Milenage, NOT secure
fn toy(k: &[u64; 2], opc: u64, w: u8, d: [u64; 3]) -> u64 {
    let mut h = k[0] ^ (w as u64).wrapping_mul(0x9E37_79B9_7F4A_7C15);
    for x in [opc, d[0], d[1], d[2], k[1]] {
        h = (h ^ x).wrapping_mul(0xff51_afd7_ed55_8ccd);
        h ^= h >> 29;
    }
    h
}
type Autn = (u64, u16, u64); // (SQN xor AK, AMF, MAC)
struct Av { rand: u64, xres: u64, autn: Autn } // CK, IK omitted
struct Home { k: [u64; 2], opc: u64, seq: u64, ind: u64, f: F }
impl Home {
    fn vector(&mut self, rand: u64) -> Av {
        self.seq += 1;
        self.ind = (self.ind + 1) % 4; // SQN = SEQ || IND, array size 4
        let (sqn, amf) = (self.seq << 2 | self.ind, 0x8000u16);
        let g = |w, d| (self.f)(&self.k, self.opc, w, d);
        let ak = g(F5, [rand, 0, 0]) & M48;
        Av { rand, xres: g(F2, [rand, 0, 0]), autn: (sqn ^ ak, amf, g(F1, [sqn, rand, amf as u64])) }
    }
    fn resync(&mut self, rand: u64, (conc, mac_s): (u64, u64)) -> bool {
        let g = |w, d| (self.f)(&self.k, self.opc, w, d);
        let sqn_ms = conc ^ (g(F5S, [rand, 0, 0]) & M48);
        let ok = g(F1S, [sqn_ms, rand, 0]) == mac_s; // AMF = 0 in MAC-S
        if ok { self.seq = sqn_ms >> 2; }
        ok
    }
}
#[derive(Clone, Copy)]
struct Card { k: [u64; 2], opc: u64, arr: [u64; 4], top: u64, f: F }
#[derive(Debug, PartialEq)]
enum Out { Ok(u64), MacFail, Sync((u64, u64)) } // RES, or AUTS = (Conc(SQN_ms), MAC-S)
impl Card {
    fn challenge(&mut self, rand: u64, a: Autn) -> Out {
        let g = |w, d| (self.f)(&self.k, self.opc, w, d);
        let sqn = (a.0 ^ g(F5, [rand, 0, 0])) & M48; // undo the anonymity key
        if g(F1, [sqn, rand, a.1 as u64]) != a.2 { return Out::MacFail; } // network not authentic
        let (seq, ind) = (sqn >> 2, (sqn & 3) as usize);
        if seq <= self.arr[ind] { // stale: report highest accepted SQN
            return Out::Sync((self.top ^ (g(F5S, [rand, 0, 0]) & M48), g(F1S, [self.top, rand, 0])));
        }
        self.arr[ind] = seq;
        if seq > self.top >> 2 { self.top = sqn; }
        Out::Ok(g(F2, [rand, 0, 0]))
    }
}
fn main() {
    let (k, opc) = ([0x0123_4567_89ab_cdef, 0xfedc_ba98_7654_3210], 0xa5a5_1234);
    let mut home = Home { k, opc, seq: 0, ind: 0, f: toy };
    let mut card = Card { k, opc, arr: [0; 4], top: 0, f: toy };
    let vs: Vec<Av> = (0..4).map(|i| home.vector(1000 + i)).collect();
    // happy path, out of order (v2 then v1)
    assert_eq!(card.challenge(vs[2].rand, vs[2].autn), Out::Ok(vs[2].xres));
    assert_eq!(card.challenge(vs[1].rand, vs[1].autn), Out::Ok(vs[1].xres));
    // replay: SQN check fails
    assert!(matches!(card.challenge(vs[2].rand, vs[2].autn), Out::Sync(_)));
    // tampered AUTN, wrong K: MAC check fails
    let mut bad = vs[3].autn; bad.2 ^= 1;
    assert_eq!(card.challenge(vs[3].rand, bad), Out::MacFail);
    bad = vs[3].autn; bad.0 ^= 4;
    assert_eq!(card.challenge(vs[3].rand, bad), Out::MacFail);
    let mut other = Card { k: [1, 2], ..card };
    assert_eq!(other.challenge(vs[3].rand, vs[3].autn), Out::MacFail);
    // home restored from backup: resync, then success
    for i in 0..8 { let v = home.vector(2000 + i); card.challenge(v.rand, v.autn); }
    let mut old = Home { k, opc, seq: 0, ind: 0, f: toy };
    let v = old.vector(3000);
    let Out::Sync(auts) = card.challenge(v.rand, v.autn) else { panic!() };
    assert!(!old.resync(v.rand, (auts.0, auts.1 ^ 1))); // forged AUTS rejected
    assert!(old.resync(v.rand, auts));
    let v = old.vector(3001);
    assert_eq!(card.challenge(v.rand, v.autn), Out::Ok(v.xres));
    // ns per card-side check, success path
    let (v, n) = (home.vector(9), 2_000_000u32);
    let (t, c0) = (Instant::now(), Card { arr: [0; 4], top: 0, ..card });
    for _ in 0..n { let mut c = c0; black_box(c.challenge(black_box(v.rand), black_box(v.autn))); }
    println!("{:.1} ns per check", t.elapsed().as_nanos() as f64 / n as f64);
}

Complexity and constants

  • Home per vector and card per success: f1 to f5 (Milenage: 5 AES-128 blocks with OPc stored, read off the TS 35.206 equations: TEMP, OUT1 to OUT4). A bad MAC costs the card 3 blocks.
  • One home request per batch, one challenge and one response per login; a resync adds a home round trip.
  • Toy functions: 8.5 ns per card-side check (3 toy calls, success path, M4 Pro, release build). Real cards are bound by the secure element.
  • Wire: RAND 16 B, AUTN 16 B, RES 4-16 B.

Efficiency

  • Cost: about 5 AES blocks per login on card and home; the real cost is the home round trip and the SQN state. Bound: latency to home.
  • Levers:
    • Vector batches — one home round trip serves n logins (TS 33.102; 5G AKA does not pre-fetch, TS 33.501) — UMTS and LTE style cores.
    • OPc stored, not recomputed — one AES block per call; TS 35.206 recommends computing it off the card — personalisation.
    • MAC check before the SQN check — a forged challenge costs least — constrained devices.
    • SQN array with indices — fewer resync round trips when vectors arrive out of order — several serving nodes per subscriber.
  • Naive → standard → state of the art:
    • Challenge-response, network never proven · fake network possible
    • MAC but no SQN · replayable
    • AKA with SQN array, AUTS resync, home confirmation (5G) · 1 home round trip per batch or login

Implementation notes

  • Draw RAND from a CSPRNG. Compare MAC and RES in constant time (the toy code does not).
  • Keep K and OPc in an HSM at home; personalisation files hold every K.
  • Persist SQN_HE before releasing vectors; a restore from backup yields stale vectors (the resync path in the code).
  • A vector is used once; the exception is retransmitting it while no response arrived (TS 33.102 clause 6.3.3).
  • 5G derives K_AUSF, then K_SEAF (binds the serving network name) with a KDF (TS 33.501 Annex A).

Pitfalls

  • AK conceals SQN only against passive attacks (TS 33.102 clause 6.3.2); a replayed challenge makes the card reuse the same AK for different SQN_MS, leaking SQN bits via AUTS. Annex J (informative) adds MAC-S to f5** to prevent it.
  • 5G: the device checks AMF bit 0 in AUTN is 1; the null SUCI scheme leaves the identity unconcealed.
  • GSM AKA authenticates the SIM to the network only (TS 33.102 definitions); nothing proves the network to the SIM, the gap a fake base station uses.
  • No resync path: a restored home counter locks subscribers out.

Variants and alternatives

EAP-AKA (RFC 4187), EAP-AKA' (RFC 9048)same exchange inside EAP for Wi-Fi and other non-3GPP access; the prime variant binds keys to the network name.
SCRAM, PAKEfor passwords, not chip keys.Mutual TLSpublic-key alternative when certificates are practical.

In production

  • Cellular networks from GSM to 5G, with SIM, USIM and secure elements.
  • Trusted and untrusted non-3GPP (Wi-Fi) access to the 5G core (TS 33.501 clauses 7, 7A).
  • Standalone non-public networks: AKA-based or other methods such as EAP-TLS (TS 33.501 Annex I).
  • Open-source cores (free5GC, Open5GS), RADIUS servers (FreeRADIUS) and supplicants (wpa_supplicant) implement the flow.

Implementations

namelangrepoinstallverdictlicencehealthas_ofnotes
open5gsCopen5gs/open5gs—————open-source mobile core network
free5GCGofree5gc/free5gc—————open-source 5G core, Linux Foundation project
hostap (wpa_supplicant)Cw1.fi/wpa_supplicant—————EAP peer method for EAP-AKA and EAP-AKA'
FreeRADIUSCFreeRADIUS/freeradius-server—————EAP-AKA' server module
milenageRustolivierlemoal/milenage-rscargo add milenage————MILENAGE algorithm set

Sources

  • 3GPP, ETSI TS 133 102 V19.1.0 (2026-01), 3G security; Security architecture. pdf
  • 3GPP, ETSI TS 133 501 V19.7.0 (2026-08), Security architecture and procedures for 5G System. pdf
  • 3GPP, ETSI TS 135 206 V19.0.0 (2025-10), Specification of the MILENAGE algorithm set, Document 2: Algorithm specification. pdf
  • 3GPP, ETSI TS 135 231 V19.0.0 (2026-01), Specification of the TUAK algorithm set, Document 1: Algorithm specification. pdf
  • Arkko, Haverinen, Extensible Authentication Protocol Method for 3rd Generation Authentication and Key Agreement (EAP-AKA), RFC 4187, 2006. rfc
  • Arkko, Lehtovirta, Torvinen, Eronen, Improved Extensible Authentication Protocol Method for 3GPP Mobile Network Authentication and Key Agreement (EAP-AKA'), RFC 9048, 2021. rfc

Builds on

Used by

Systems (6):

Alternatives and related