Apple GSA Login — the SRP6a Variant

About the author. I’m Colin Xiao — an independent reverse engineer, mostly working solo on contract jobs. I dig into compiled code of every kind, including but not limited to: Android and iOS apps, x86/x64/ARM native binaries, packers and obfuscators, DRM and license checks, network protocols and APIs, crypto and signature algorithms, malware, game binaries, and embedded/IoT firmware. I also handle large-scale data collection and vulnerability research. If you have any project in mind, I’d be glad to take it on — whatever it is, as long as it’s within my wheelhouse. Just reach out. You can reach me on X at @colinxiao_lab or on Telegram at https://t.me/ColinXiao666.

This research is published for educational and defensive purposes. The target sample is anonymized throughout.


Apple GSA Login — the SRP6a Variant

The GSA login protocol

Apple’s login flow has been discussed and analyzed publicly many times. I’ve long wanted to log into an Apple account from any platform, but I have to admit that’s almost impossible today. Apple’s GSA login is a modified standard SRP6a. I meant to write the details up back in 2017; COVID-19 finally gave me the time.

Reversing iTunes to reconstruct every SRP parameter is very hard. But iOS/macOS have plaintext helper functions, and by logging the key data points I reconstructed Apple’s SRP login flow. The parameters:

N   A large 2048-bit prime, from RFC5054-2048. The document displays it big-endian.
g   2 in this case
k   multiplier, k = H(N,g)
s   salt, sent by the server
I   username
p   password — the value used in the SRP arithmetic is NOT the plaintext
H() SHA256
^   exponentiation
u   intermediate result
a,b client and server 32-byte private keys
x   intermediate result computed locally from the private key
v   verifier
K   shared key
M1,M2: password verification
SPD  login result, AES_CBC-encrypted

N, g

From RFC5054-2048. There are multiple copies in memory; Apple doesn’t store it big-endian everywhere.

 The hexadecimal value for the prime is:

          AC6BDB41 324A9A9B F166DE5E 1389582F AF72B665 1987EE07 FC319294
          3DB56050 A37329CB B4A099ED 8193E075 7767A13D D52312AB 4B03310D
          CD7F48A9 DA04FD50 E8083969 EDB767B0 CF609517 9A163AB3 661A05FB
          D5FAAAE8 2918A996 2F0B93B8 55F97993 EC975EEA A80D740A DBF4FF74
          7359D041 D5C33EA7 1D281E44 6B14773B CA97B43A 23FB8016 76BD207A
          436C6481 F1D2B907 8717461A 5B9D32E6 88F87748 544523B5 24B0D57D
          5EA77A27 75D2ECFA 032CFBDB F52FB378 61602790 04E57AE6 AF874E73
          03CE5329 9CCC041C 7BC308D8 2A5698F3 A8D0C382 71AE35F8 E9DBFBB6
          94B5C803 D89F7AE4 35DE236D 525F5475 9B65E372 FCD68EF2 0FA7111F
          9E4AFF73

 The generator is: 2.

k = H(N,g)

From logging, the input to k = H(N,g) is:

ac6bdb41324a9a9bf166de5e1389582faf72b6651987ee07fc3192943db56050a37329cbb4a099ed8193e0757767a13dd52312ab4b03310dcd7f48a9da04fd50e8083969edb767b0cf6095179a163ab3661a05fbd5faaae82918a9962f0b93b855f97993ec975eeaa80d740adbf4ff747359d041d5c33ea71d281e446b14773bca97b43a23fb801676bd207a436c6481f1d2b9078717461a5b9d32e688f87748544523b524b0d57d5ea77a2775d2ecfa032cfbdbf52fb3786160279004e57ae6af874e7303ce53299ccc041c7bc308d82a5698f3a8d0c38271ae35f8e9dbfbb694b5c803d89f7ae435de236d525f54759b65e372fcd68ef20fa7111f9e4aff7300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002

N and g are big-endian formatted, zero-padded on the high end to 2048 bits, concatenated, then hashed.

u = H(A,B)

A and B are two big integers, big-endian, concatenated directly: sha256(concat(A,B)).

x = H(s | H(I | “:” | p))

Apple drops the username:

    def calculateX(salt,I,P):
        h1 = hashlib.sha256()
        h1.update(':')
        h1.update(P)
        digest1 = h1.digest()
        
        h2 = hashlib.sha256()
        h2.update(salt)
        h2.update(digest1)
        x = h2.digest()
        return x

P

P is the result of sha256 then pbkdf2-sha256, with the iteration count taken from the i field in the first response:

<key>i</key> 
<integer>20430</integer> 
<key>s</key> 
<data>RcFwuW4AUEWv3Lv0k6Gemg==</data> 
<key>sp</key> 
<string>s2k</string> 
<key>c</key> 
<string>i-35f-c53ff631-25ba-4395-a89a-2b66fbdcf083:NC</string>

i is the iteration count; Apple’s servers periodically bump it — this prevents offline table lookups of plaintext passwords after a database breach. (Standard SRP uses the plaintext as P.)

M1, M2

def calculateM1(N,g,username,salt,A,B,K):
    i1 = hashlib.sha256(N).digest()
    i2 = hashlib.sha256(g).digest()
    i = i1 ^ i2
    h = hashlib.sha256()
    h.update(i)
    h.update(hashlib.sha256(username).digest())
    h.update(salt)
    h.update(A)
    h.update(B)
    h.update(K)
    m1 = h.digest()
    return m1
    
def calculateM2(A,M1,K):
    h = hashlib.sha256()
    h.update(A)
    h.update(M1)
    h.update(K)
    m2 = h.digest()
    return m2

Remaining fields

The remaining fields match the standard algorithm, so I’ll skip them.

SPD decryption

SPD is encrypted with AES-CBC, PKCS#7-padded. You need a key and IV to decrypt. During SRP, client and server share K. The AES key and IV are derived via HMAC, using the SRP K as the HMAC key and the MAC info strings:

 "extra data key:"
 "extra data iv:"

After decrypting, strip the PKCS#7 padding to get the various password tokens — at which point a normal-account login is done. Second-factor auth via iDevice notification or SMS is out of scope here; after 2FA passes you get a new SPD dictionary.

How hard is fully offline login?

Apple added two-factor authentication in iOS 9. Accounts with 2FA enabled must use SRP to log in on-device. The device info sent at login:

UDID           Device unique ID — Apple records the ID of every device it sells. On Windows it's trivially spoofable.
X-Apple-I-MD   OTP — a one-time password that rotates every minute.
X-Apple-I-MD-M Device fingerprint — authorized-device info from the local adi.pb.

IMD and IMDM always appear as a pair. There’s currently no public reference for how they’re computed. As far as I know, on Windows it’s protected by heavily obfuscated code in fpcore.dll, and nobody has claimed to recover the algorithm. On iDevices the computation is complex and heavily obfuscated too. To log into an Apple account on Linux/Android, one option is to use an iDevice to compute the OTP — but that requires remote calls. Writing an emulator to run iTunes might work, but the effort speaks for itself.

I’ve also released a Go implementation (gsalogin).


Colin Xiao — I take on contract reverse-engineering work, and writeups like this are how I show what I can do. If you need help with anything in this space — including but not limited to the areas I cover above — I’m available. No project is too big or too small — if it’s reverse engineering and I can do it, I’ll take it. DM me on X (@colinxiao_lab) or Telegram (@ColinXiao666).

Get in touch