class Milenage::Kernel

The core class for calculating Milenage security functions.

To use this class, determine the operator variant algorithm configuration field (OP) or the pre-encrypted version of the same (OPc) and also determine the user-specific key value (KEY). Create an instance of the kernel class, passing the key, then set the OP/OPc as needed:

m = Milenage::Kernel.new(KEY)
m.op = OP # or m.opc = OPc

At this point, the kernel instance can be used to calculate security functions for the user:

mac_a = m.f1(RAND, SQN, AMF)
mac_s = m.f1_star(RAND, SQN, AMF)
res = m.f2(RAND)
ck = m.f3(RAND)
ik = m.f4(RAND)
ak = m.r5(RAND) # or ak = m.f5_star(RAND)

You may change the OP/OPc at any time if needed:

m.op = NEW_OP
m.opc = NEW_OPc

Public Class Methods

new(key) click to toggle source

Create a single user’s kernel instance, remember to set OP or OPc before attempting to use the security functions.

To change the algorithm variables as described in TS 35.206 subclass this Kernel class and modify ‘@c`, `@r` or `@kernel` after calling super. E.G.

class MyKernel < Kernel
  def initialize(key)
    super
    @r = [10, 20, 30, 40, 50]
  end
end

When doing this, ‘@kernel` should be set to a 128-bit MAC function with the same API as `OpenSSL::Cipher`, if this is not the case, you may need to overload {#enc} as well to match the API.

# File lib/milenage.rb, line 47
def initialize(key)
  fail "KEY must be 128 bits" unless key.each_byte.to_a.length == 16
  @key = key
  @c = [0, 1, 2, 4, 8].map { |i| [0, i].pack("Q>2") }
  @r = [64, 0, 32, 64, 96]
  @kernel = OpenSSL::Cipher::AES128.new(:ECB)
end

Public Instance Methods

f1(rand, sqn, amf) click to toggle source

Calculate the network authentication code (MAC-A)

# File lib/milenage.rb, line 82
def f1(rand, sqn, amf)
  step_a(rand, sqn, amf)[0..7]
end
f1_star(rand, sqn, amf) click to toggle source

Calculate the resync authentication code (MAC-S)

# File lib/milenage.rb, line 87
def f1_star(rand, sqn, amf)
  step_a(rand, sqn, amf)[8..15]
end
f2(rand) click to toggle source

Calculate the response (RES)

# File lib/milenage.rb, line 92
def f2(rand)
  step_b(rand)[8..15]
end
f3(rand) click to toggle source

Calculate the confidentiallity key (CK)

# File lib/milenage.rb, line 97
def f3(rand)
  step_c(rand)
end
f4(rand) click to toggle source

Calculate the integrity key (IK)

# File lib/milenage.rb, line 102
def f4(rand)
  step_d(rand)
end
f5(rand) click to toggle source

Calculate the anonymity key (AK)

# File lib/milenage.rb, line 107
def f5(rand)
  step_b(rand)[0..5]
end
f5_star(rand) click to toggle source

Calculate the anonymity resynch key (AK)

# File lib/milenage.rb, line 112
def f5_star(rand)
  step_e(rand)[0..5]
end
op=(op) click to toggle source

Set the Operator Variant Algorithm Configuration field.

Either this or {#opc=} must be called before any of the security functions are evaluated.

# File lib/milenage.rb, line 59
def op=(op)
  fail "OP must be 128 bits" unless op.each_byte.to_a.length == 16
  @opc = xor(enc(op), op)
end
opc() click to toggle source

Standard getter for the OPc.

# File lib/milenage.rb, line 76
def opc
  fail "Must set OP or OPc before retrieving OPc" unless @opc
  @opc
end
opc=(opc) click to toggle source

Set the precomputed encoded Operator Variant Algorithm Configuration field. Note that there are no checks that this value is even feasible for the given key.

Either this or {#op=} must be called before any of the security functions are evaluated.

# File lib/milenage.rb, line 70
def opc=(opc)
  fail "OPc must be 128 bits" unless opc.each_byte.to_a.length == 16
  @opc = opc
end

Private Instance Methods

enc(data) click to toggle source
# File lib/milenage.rb, line 118
def enc(data)
  @kernel.encrypt
  @kernel.key = @key
  @kernel.padding = 0
  return (@kernel.update(data) + @kernel.final)
end
roll(data, count) click to toggle source
# File lib/milenage.rb, line 156
def roll(data, count)
  data = data.unpack("Q>2")
  if count >= 64
    data[0], data[1] = data[1], data[0]
    count -= 64
  end

  out = [0, 0]
  out[0] = (data[0] << count) & 0xFFFFFFFFFFFFFFFF
  out[0] |= data[1] >> (64 - count)
  out[1] = (data[1] << count) & 0xFFFFFFFFFFFFFFFF
  out[1] |= data[0] >> (64 - count)

  return out.pack("Q>2")
end
step_0(rand) click to toggle source
# File lib/milenage.rb, line 125
def step_0(rand)
  fail "Must set OP or OPc before calculating hashes" unless @opc
  fail "RAND must be 128 bits" unless rand.each_byte.to_a.length == 16
  enc(xor(rand, @opc))
end
step_a(rand, sqn, amf) click to toggle source
# File lib/milenage.rb, line 131
def step_a(rand, sqn, amf)
  fail "Must set OP or OPc before calculating hashes" unless @opc
  fail "SQN must be 48 bits" unless sqn.each_byte.to_a.length == 6
  fail "AMF must be 16 bits" unless amf.each_byte.to_a.length == 2
  tmp = (sqn + amf + sqn + amf)
  tmp = xor(tmp, @opc)
  tmp = roll(tmp, @r[0])
  tmp = xor(xor(tmp, @c[0]), step_0(rand))
  tmp = enc(tmp)
  xor(tmp, @opc)
end
step_b(rand) click to toggle source
# File lib/milenage.rb, line 143
def step_b(rand); step_x(rand, 1); end
step_c(rand) click to toggle source
# File lib/milenage.rb, line 144
def step_c(rand); step_x(rand, 2); end
step_d(rand) click to toggle source
# File lib/milenage.rb, line 145
def step_d(rand); step_x(rand, 3); end
step_e(rand) click to toggle source
# File lib/milenage.rb, line 146
def step_e(rand); step_x(rand, 4); end
step_x(rand, idx) click to toggle source
# File lib/milenage.rb, line 148
def step_x(rand, idx)
  tmp = xor(step_0(rand), @opc)
  tmp = roll(tmp, @r[idx])
  tmp = xor(tmp, @c[idx])
  tmp = enc(tmp)
  tmp = xor(tmp, @opc)
end
xor(a, b) click to toggle source
# File lib/milenage.rb, line 172
def xor(a, b)
  a.each_byte.to_a.zip(b.bytes).map do |a, b|
    a ^ b
  end.pack("c*")
end