Module: Net::NTLM

Defined in:
lib/net/ntlm.rb,
lib/net/ntlm/blob.rb,
lib/net/ntlm/field.rb,
lib/net/ntlm/client.rb,
lib/net/ntlm/string.rb,
lib/net/ntlm/message.rb,
lib/net/ntlm/version.rb,
lib/net/ntlm/int16_le.rb,
lib/net/ntlm/int32_le.rb,
lib/net/ntlm/int64_le.rb,
lib/net/ntlm/field_set.rb,
lib/net/ntlm/encode_util.rb,
lib/net/ntlm/message/type0.rb,
lib/net/ntlm/message/type1.rb,
lib/net/ntlm/message/type2.rb,
lib/net/ntlm/message/type3.rb,
lib/net/ntlm/client/session.rb,
lib/net/ntlm/security_buffer.rb

Defined Under Namespace

Modules: VERSION Classes: Blob, Client, EncodeUtil, Field, FieldSet, Int16LE, Int32LE, Int64LE, Message, SecurityBuffer, String

Constant Summary collapse

LM_MAGIC =
"KGS!@\#$%"
TIME_OFFSET =
11644473600
MAX64 =
0xffffffffffffffff
BLOB_SIGN =
0x00000101
SSP_SIGN =
"NTLMSSP\0"
FLAGS =
{
    :UNICODE              => 0x00000001,
    :OEM                  => 0x00000002,
    :REQUEST_TARGET       => 0x00000004,
    :MBZ9                 => 0x00000008,
    :SIGN                 => 0x00000010,
    :SEAL                 => 0x00000020,
    :NEG_DATAGRAM         => 0x00000040,
    :NETWARE              => 0x00000100,
    :NTLM                 => 0x00000200,
    :NEG_NT_ONLY          => 0x00000400,
    :MBZ7                 => 0x00000800,
    :DOMAIN_SUPPLIED      => 0x00001000,
    :WORKSTATION_SUPPLIED => 0x00002000,
    :LOCAL_CALL           => 0x00004000,
    :ALWAYS_SIGN          => 0x00008000,
    :TARGET_TYPE_DOMAIN   => 0x00010000,
    :NTLM2_KEY            => 0x00080000,
    :TARGET_INFO          => 0x00800000,
    :KEY128               => 0x20000000,
    :KEY_EXCHANGE         => 0x40000000,
    :KEY56                => 0x80000000
}.freeze
FLAG_KEYS =
FLAGS.keys.sort{|a, b| FLAGS[a] <=> FLAGS[b] }
DEFAULT_FLAGS =
{
    :TYPE1 => FLAGS[:UNICODE] | FLAGS[:OEM] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY],
    :TYPE2 => FLAGS[:UNICODE],
    :TYPE3 => FLAGS[:UNICODE] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY]
}

Class Method Summary collapse

Class Method Details

.apply_des(plain, keys) ⇒ Object



102
103
104
105
106
107
108
109
# File 'lib/net/ntlm.rb', line 102

def apply_des(plain, keys)
  dec = OpenSSL::Cipher::Cipher.new("des-cbc")
  dec.padding = 0
  keys.map {|k|
    dec.key = k
    dec.encrypt.update(plain) + dec.final
  }
end

.gen_keys(str) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Not sure what this is doing

Parameters:

  • str (String)

    String to generate keys for



94
95
96
97
98
99
100
# File 'lib/net/ntlm.rb', line 94

def gen_keys(str)
  split7(str).map{ |str7|
    bits = split7(str7.unpack("B*")[0]).inject('')\
      {|ret, tkn| ret += tkn + (tkn.gsub('1', '').size % 2).to_s }
    [bits].pack("B*")
  }
end

.lm_hash(password) ⇒ Object

Generates a Lan Manager Hash

Parameters:

  • password (String)

    The password to base the hash on



113
114
115
116
# File 'lib/net/ntlm.rb', line 113

def lm_hash(password)
  keys = gen_keys password.upcase.ljust(14, "\0")
  apply_des(LM_MAGIC, keys).join
end

.lm_response(arg) ⇒ Object



143
144
145
146
147
148
149
150
151
152
153
# File 'lib/net/ntlm.rb', line 143

def lm_response(arg)
  begin
    hash = arg[:lm_hash]
    chal = arg[:challenge]
  rescue
    raise ArgumentError
  end
  chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
  keys = gen_keys hash.ljust(21, "\0")
  apply_des(chal, keys).join
end

.lmv2_response(arg, opt = {}) ⇒ Object



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/net/ntlm.rb', line 198

def lmv2_response(arg, opt = {})
  key = arg[:ntlmv2_hash]
  chal = arg[:challenge]

  chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)

  if opt[:client_challenge]
    cc  = opt[:client_challenge]
  else
    cc = rand(MAX64)
  end
  cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)

  OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + cc) + cc
end

.ntlm2_session(arg, opt = {}) ⇒ Object



214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/net/ntlm.rb', line 214

def ntlm2_session(arg, opt = {})
  begin
    passwd_hash = arg[:ntlm_hash]
    chal = arg[:challenge]
  rescue
    raise ArgumentError
  end
  chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)

  if opt[:client_challenge]
    cc = opt[:client_challenge]
  else
    cc = rand(MAX64)
  end
  cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)

  keys = gen_keys(passwd_hash.ljust(21, "\0"))
  session_hash = OpenSSL::Digest::MD5.digest(chal + cc).slice(0, 8)
  response = apply_des(session_hash, keys).join
  [cc.ljust(24, "\0"), response]
end

.ntlm_hash(password, opt = {}) ⇒ Object

Generate a NTLM Hash

Parameters:

  • password (String)

    The password to base the hash on

  • opt (Hash) (defaults to: {})

    a customizable set of options

Options Hash (opt):

  • :unicode (Object) — default: false

    Unicode encode the password



121
122
123
124
125
126
127
# File 'lib/net/ntlm.rb', line 121

def ntlm_hash(password, opt = {})
  pwd = password.dup
  unless opt[:unicode]
    pwd = EncodeUtil.encode_utf16le(pwd)
  end
  OpenSSL::Digest::MD4.digest pwd
end

.ntlm_response(arg) ⇒ Object



155
156
157
158
159
160
161
# File 'lib/net/ntlm.rb', line 155

def ntlm_response(arg)
  hash = arg[:ntlm_hash]
  chal = arg[:challenge]
  chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
  keys = gen_keys hash.ljust(21, "\0")
  apply_des(chal, keys).join
end

.ntlmv2_hash(user, password, target, opt = {}) ⇒ Object

Generate a NTLMv2 Hash

Parameters:

  • user (String)

    The username

  • password (String)

    The password

  • target (String)

    The domain or workstaiton to authenticate to

  • opt (Hash) (defaults to: {})

    a customizable set of options

Options Hash (opt):

  • :unicode (Object) — default: false

    Unicode encode the domain



134
135
136
137
138
139
140
141
# File 'lib/net/ntlm.rb', line 134

def ntlmv2_hash(user, password, target, opt={})
  ntlmhash = ntlm_hash(password, opt)
  userdomain = user.upcase + target
  unless opt[:unicode]
    userdomain = EncodeUtil.encode_utf16le(userdomain)
  end
  OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmhash, userdomain)
end

.ntlmv2_response(arg, opt = {}) ⇒ Object



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/net/ntlm.rb', line 163

def ntlmv2_response(arg, opt = {})
  begin
    key = arg[:ntlmv2_hash]
    chal = arg[:challenge]
    ti = arg[:target_info]
  rescue
    raise ArgumentError
  end
  chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)

  if opt[:client_challenge]
    cc  = opt[:client_challenge]
  else
    cc = rand(MAX64)
  end
  cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)

  if opt[:timestamp]
    ts = opt[:timestamp]
  else
    ts = Time.now.to_i
  end
  # epoch -> milsec from Jan 1, 1601
  ts = 10_000_000 * (ts + TIME_OFFSET)

  blob = Blob.new
  blob.timestamp = ts
  blob.challenge = cc
  blob.target_info = ti

  bb = blob.serialize

  OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + bb) + bb
end

.pack_int64le(val) ⇒ Object

Conver the value to a 64-Bit Little Endian Int

Parameters:

  • val (String)

    The string to convert



76
77
78
# File 'lib/net/ntlm.rb', line 76

def pack_int64le(val)
    [val & 0x00000000ffffffff, val >> 32].pack("V2")
end

.split7(str) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Builds an array of strings that are 7 characters long

Parameters:

  • str (String)

    The string to split



83
84
85
86
87
88
89
# File 'lib/net/ntlm.rb', line 83

def split7(str)
  s = str.dup
  until s.empty?
    (ret ||= []).push s.slice!(0, 7)
  end
  ret
end