├── README ├── VERSION ├── input ├── milenage.py └── sample /README: -------------------------------------------------------------------------------- 1 | # 2 | # milenage.py 3 | # Program to generate GSM Authentication Triplets 4 | # Author: mmehra@juniper.net 5 | # 6 | # https://github.com/mmehra/milenage 7 | # 8 | 9 | This program can be used to generate GSM authentication triplets 10 | using milenage algorithm specified in 3GPP TS 55.205 v9.0.0. These 11 | authentication triplets can be used to test EAP-SIM with real UE 12 | and freeradius server. 13 | 14 | Softwares like gemalto, etc are available to provision (U)SIM with 15 | IMSI, Ki, Op values. However very few softwares are available to 16 | generate GSM auth triplets. AGSM (http://agsm.sourceforge.net) is 17 | available but it does not work with all card readers. This small 18 | utility does the job, generates SRES, Kc given Ki, Op and RAND. 19 | 20 | 21 | Directory Structure: 22 | milenage/ 23 | |--> milenage.py Milenage algorithm implementation 24 | |--> input Sample input file 25 | |--> sample Sample test-sets from 55.205 26 | |--> VERSION Version 27 | 28 | 29 | Usage: 30 | The program expects an input file with Ki, Op and rand values. 31 | Please refer to sample input file for the format 32 | ./milenage 33 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.1 2 | -------------------------------------------------------------------------------- /input: -------------------------------------------------------------------------------- 1 | # 2 | # Input file for milenage.py. This program understands 3 | # input file in following format. Multiple keysets 4 | # can be specified 5 | # 6 | # ki=128 bit encryption key 7 | # op=128 bit operator specific constant 8 | # rand=128 bit random number 9 | # 10 | # 11 | 12 | ki=90dca4eda45b53cf0f12d7c9c3bc6a89 13 | op=3ffcfe5b7b1111589920d3528e84e655 14 | rand=9fddc72092c6ad036b6e464789315b78 15 | 16 | ki=5122250214c33e723a5dd523fc145fc0 17 | op=c9e8763286b5b9ffbdf56e1297d0887b 18 | rand=81e92b6c0ee0e12ebceba8d92a99dfa5 19 | 20 | ki=b73a90cbcf3afb622dba83c58a8415df 21 | op=b672047e003bb952dca6cb8af0e5b779 22 | rand=b120f1c1a0102a2f507dd543de68281f 23 | 24 | -------------------------------------------------------------------------------- /milenage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # $Id$ 4 | # 5 | # This program implements GSM milenage algorithm, specified 6 | # in 3GPP TS 55.205, for generating GSM auth triplets. This 7 | # function runs on HLR for providing auth info to SGSN/MME/ 8 | # Radius server for UE/MS authentication. 9 | # 10 | # Author: mmehra@juniper.net 11 | # 12 | 13 | import sys 14 | import binascii 15 | from Crypto.Cipher import AES 16 | from itertools import izip 17 | 18 | 19 | #Our macro 20 | __XOR__ = lambda x, y: chr(ord(x) ^ ord(y)) 21 | 22 | 23 | def LogicalXOR(str1, str2): 24 | '''Function to XOR two strings''' 25 | return ''.join(__XOR__(x, y) for (x,y) in izip(str1, str2)) 26 | 27 | 28 | def AESEncrypt(key, buf): 29 | '''Encrypt buffer using AES-SHA1 128 algorithm 30 | @key: Key to be used for encryption 31 | @buf: Buffer to be encrypted''' 32 | encryptor = AES.new(key, AES.MODE_CBC) 33 | return encryptor.encrypt(buf) 34 | 35 | 36 | def GsmMilenageGenOpc(ki, op): 37 | '''Generate Opc using Ki and Op 38 | @ki: 128-bit subscriber key 39 | @op: 128-bit operator variant''' 40 | opc = AESEncrypt(ki, op) 41 | return LogicalXOR(opc, op) 42 | 43 | 44 | def GsmMilenageF2345(ki, opc, rand): 45 | '''Milenage f2, f3, f4, f5, f5* algorithms''' 46 | i = 0 47 | tmp1 = LogicalXOR(rand, opc) 48 | tmp2 = AESEncrypt(ki, tmp1) 49 | tmp1 = LogicalXOR(tmp2, opc) 50 | tmp1 = tmp1[:15] + chr(ord(tmp1[15]) ^ 1) 51 | tmp3 = AESEncrypt(ki, tmp1) 52 | tmp3 = LogicalXOR(tmp3, opc) 53 | res = tmp3[8:] 54 | 55 | #F3 - to calculate ck 56 | ck_map = {} 57 | for i in range(16): 58 | ck_map[(i+12)%16] = __XOR__(tmp2[i], opc[i]) 59 | ck_map[15] = __XOR__(ck_map[15], chr(2)) 60 | tmp1 = ''.join(val for val in ck_map.values()) 61 | ck = AESEncrypt(ki, tmp1) 62 | ck = LogicalXOR(ck, opc) 63 | 64 | #F4 - to calculate ik 65 | ik_map = {} 66 | for i in range(16): 67 | ik_map[(i+8)%16] = __XOR__(tmp2[i], opc[i]) 68 | ik_map[15] = __XOR__(ik_map[15], chr(4)) 69 | tmp1 = ''.join(val for val in ik_map.values()) 70 | ik = AESEncrypt(ki, tmp1) 71 | ik = LogicalXOR(ik, opc) 72 | 73 | return res, ck, ik 74 | 75 | 76 | def GsmMilenage(ki, opc, rand): 77 | '''Generate GSM-Milenage (3GPP TS 55.205) auth triplet 78 | @ki : 128-bit subscriber key 79 | @opc : 128-bit operator variant algorithm configuration 80 | @rand: 128-bit random challenge''' 81 | 82 | res, ck, ik = GsmMilenageF2345(ki, opc, rand) 83 | 84 | #Calculate sres 85 | sres_map = {} 86 | for idx in range(4): 87 | sres_map[idx] = __XOR__(res[idx], res[idx+4]) 88 | sres = ''.join(val for val in sres_map.values()) 89 | 90 | #Calculate kc 91 | kc_map = {} 92 | for idx in range(8): 93 | kc_map[idx] = __XOR__(__XOR__(ck[idx], ck[idx+8]), 94 | __XOR__(ik[idx], ik[idx+8])) 95 | kc = ''.join(val for val in kc_map.values()) 96 | 97 | return sres, kc 98 | 99 | 100 | def GenerateAuthTriplets(keyset): 101 | ki = binascii.unhexlify(keyset['ki']) 102 | op = binascii.unhexlify(keyset['op']) 103 | rand = binascii.unhexlify(keyset['rand']) 104 | 105 | #Generate opc from ki and op 106 | opc = GsmMilenageGenOpc(ki, op) 107 | 108 | #Get sres, kc 109 | sres, kc = GsmMilenage(ki, opc, rand) 110 | 111 | #Store values now 112 | keyset['opc'] = binascii.hexlify(opc) 113 | keyset['kc'] = binascii.hexlify(kc) 114 | keyset['sres'] = binascii.hexlify(sres) 115 | return 116 | 117 | 118 | def ReadMilenageInput(filename): 119 | attribs = [] 120 | keyset = {} 121 | try: 122 | fp = open(filename) 123 | except: 124 | print 'Error opening file %s'%(filename) 125 | sys.exit() 126 | 127 | for line in fp.readlines(): 128 | if line.startswith('#'): 129 | continue 130 | 131 | if line.startswith('\n'): 132 | if len(keyset): 133 | attribs.append(keyset) 134 | keyset = {} 135 | continue 136 | 137 | key, value = line.split('=') 138 | keyset[key] = value.split('\n')[0] 139 | 140 | #Validate input 141 | if len(attribs) == 0: 142 | print 'Milenage: Please provide KI/OP/RAND in input file' 143 | sys.exit() 144 | 145 | for keyset in attribs: 146 | if not keyset.has_key('ki') or \ 147 | not keyset.has_key('op') or \ 148 | not keyset.has_key('rand'): 149 | print 'Milenage: KI or OP missing in keyset' 150 | sys.exit() 151 | 152 | return attribs 153 | 154 | 155 | def PrintMilenageOutput(attribs): 156 | '''Prints input read''' 157 | idx = 1 158 | for keyset in attribs: 159 | print 'Keyset # %d'%(idx) 160 | print ' %2s: %s'%('ki', keyset['ki']) 161 | print ' %2s: %s'%('op', keyset['op']) 162 | print ' Auth Triplets: ' 163 | print ' %4s: %s'%('rand', keyset['rand']) 164 | print ' %4s: %s'%('sres', keyset['sres']) 165 | print ' %4s: %s'%('kc', keyset['kc']) 166 | print '' 167 | idx += 1 168 | return 169 | 170 | 171 | def main(): 172 | '''The main function''' 173 | if len(sys.argv) < 2: 174 | print 'Milenage: Please provide input file' 175 | return 176 | 177 | #Read input 178 | attribs = ReadMilenageInput(sys.argv[1]) 179 | 180 | #Generate auth triplets now 181 | for keyset in attribs: 182 | GenerateAuthTriplets(keyset) 183 | 184 | #Print output 185 | PrintMilenageOutput(attribs) 186 | return 187 | 188 | 189 | if __name__ == '__main__': 190 | main() 191 | -------------------------------------------------------------------------------- /sample: -------------------------------------------------------------------------------- 1 | # 2 | # Sample test sets for testing milenage.py 3 | # These test sets are copied from 3GPP TS 55.205 v9.0 4 | # 5 | # The specification can be found @ 6 | # http://www.3gpp.org/ftp/Specs/html-info/55205.htm 7 | # 8 | 9 | TEST SET # 11 10 | ------------- 11 | Ki 77b45843c88e58c10d202684515ed430 12 | RAND 4c47eb3076dc55fe5106cb2034b8cd78 13 | OP bf3286c7a51409ce95724d503bfe6e70 14 | OPc d483afae562409a326b5bb0b20c4d762 15 | MIL3G-RES aefa357beac2a87a 16 | SRES#1 44389d01 17 | SRES#2 aefa357b 18 | MIL3G-CK 908c43f0569cb8f74bc971e706c36c5f 19 | MIL3G-IK c251df0d888dd9329bcf46655b226e40 20 | Kc 82dbab7f83f063da 21 | 22 | 23 | TEST SET # 12 24 | ------------- 25 | Ki 729b17729270dd87ccdf1bfe29b4e9bb 26 | RAND 311c4c929744d675b720f3b7e9b1cbd0 27 | OP d04c9c35bd2262fa810d2924d036fd13 28 | OPc 228c2f2f06ac3268a9e616ee16db4ba1 29 | MIL3G-RES 98dbbd099b3b408d 30 | SRES#1 03e0fd84 31 | SRES#2 98dbbd09 32 | MIL3G-CK 44c0f23c5493cfd241e48f197e1d1012 33 | MIL3G-IK 0c9fb81613884c2535dd0eabf3b440d8 34 | Kc 3c66cb98cab2d33d 35 | 36 | 37 | TEST SET # 13 38 | ------------- 39 | Ki d32dd23e89dc662354ca12eb79dd32fa 40 | RAND cf7d0ab1d94306950bf12018fbd46887 41 | OP fe75905b9da47d356236d0314e09c32e 42 | OPc d22a4b4180a5325708a5ff70d9f67ec7 43 | MIL3G-RES af4a411e1139f2c2 44 | SRES#1 be73b3dc 45 | SRES#2 af4a411e 46 | MIL3G-CK 5af86b80edb70df5292cc1121cbad50c 47 | MIL3G-IK 7f4d6ae7440e18789a8b75ad3f42f03a 48 | Kc 9612b5d88a4130bb 49 | 50 | 51 | TEST SET # 14 52 | ------------- 53 | Ki af7c65e1927221de591187a2c5987a53 54 | RAND 1f0f8578464fd59b64bed2d09436b57a 55 | OP 0c7acb8d95b7d4a31c5aca6d26345a88 56 | OPc a4cf5c8155c08a7eff418e5443b98e55 57 | MIL3G-RES 7bffa5c2f41fbc05 58 | SRES#1 8fe019c7 59 | SRES#2 7bffa5c2 60 | MIL3G-CK 3f8c3f3ccf7625bf77fc94bcfd22fd26 61 | MIL3G-IK abcbae8fd46115e9961a55d0da5f2078 62 | Kc 75a150df3c6aed08 63 | 64 | 65 | TEST SET # 15 66 | ------------- 67 | Ki 5bd7ecd3d3127a41d12539bed4e7cf71 68 | RAND 59b75f14251c75031d0bcbac1c2c04c7 69 | OP f967f76038b920a9cd25e10c08b49924 70 | OPc 76089d3c0ff3efdc6e36721d4fceb747 71 | MIL3G-RES 7e3f44c7591f6f45 72 | SRES#1 27202b82 73 | SRES#2 7e3f44c7 74 | MIL3G-CK d42b2d615e49a03ac275a5aef97af892 75 | MIL3G-IK 0b3f8d024fe6bfafaa982b8f82e319c2 76 | Kc b7f92e426a36fec5 77 | 78 | 79 | TEST SET # 16 80 | ------------- 81 | Ki 6cd1c6ceb1e01e14f1b82316a90b7f3d 82 | RAND f69b78f300a0568bce9f0cb93c4be4c9 83 | OP 078bfca9564659ecd8851e84e6c59b48 84 | OPc a219dc37f1dc7d66738b5843c799f206 85 | MIL3G-RES 70f6bdb9ad21525f 86 | SRES#1 ddd7efe6 87 | SRES#2 70f6bdb9 88 | MIL3G-CK 6edaf99e5bd9f85d5f36d91c1272fb4b 89 | MIL3G-IK d61c853c280dd9c46f297baec386de17 90 | Kc 88d9de10a22004c5 91 | 92 | 93 | TEST SET # 17 94 | ------------- 95 | Ki 5122250214c33e723a5dd523fc145fc0 96 | RAND 81e92b6c0ee0e12ebceba8d92a99dfa5 97 | OP c9e8763286b5b9ffbdf56e1297d0887b 98 | OPc 981d464c7c52eb6e5036234984ad0bcf 99 | MIL3G-RES 28d7b0f2a2ec3de5 100 | SRES#1 8a3b8d17 101 | SRES#2 28d7b0f2 102 | MIL3G-CK 5349fbe098649f948f5d2e973a81c00f 103 | MIL3G-IK 9744871ad32bf9bbd1dd5ce54e3e2e5a 104 | Kc 9a8d0e883ff0887a 105 | 106 | 107 | TEST SET # 18 108 | ------------- 109 | Ki b73a90cbcf3afb622dba83c58a8415df 110 | RAND b120f1c1a0102a2f507dd543de68281f 111 | OP b672047e003bb952dca6cb8af0e5b779 112 | OPc df0c67868fa25f748b7044c6e7c245b8 113 | MIL3G-RES 479dd25c20792d63 114 | SRES#1 67e4ff3f 115 | SRES#2 479dd25c 116 | MIL3G-CK 66195dbed0313274c5ca7766615fa25e 117 | MIL3G-IK 66bec707eb2afc476d7408a8f2927b36 118 | Kc a819e577a8d6175b 119 | 120 | 121 | TEST-SET # 19 122 | ------------- 123 | Ki 90dca4eda45b53cf0f12d7c9c3bc6a89 124 | RAND 9fddc72092c6ad036b6e464789315b78 125 | OP 3ffcfe5b7b1111589920d3528e84e655 126 | OPc cb9cccc4b9258e6dca4760379fb82581 127 | MIL3G-RES a95100e2760952cd 128 | SRES#1 df58522f 129 | SRES#2 a95100e2 130 | MIL3G-CK b5f2da03883b69f96bf52e029ed9ac45 131 | MIL3G-IK b4721368bc16ea67875c5598688bb0ef 132 | Kc ed29b2f1c27f9f34 133 | --------------------------------------------------------------------------------