module Net::NTLM

Constants

BLOB_SIGN
DATA_REGEXP

Valid format for an NTLM hash composed of ‘’<LAN Manager hex digest>:<NT LAN Manager hex digest>‘`.

DEFAULT_FLAGS
FLAGS

See [2.2.2.5 NEGOTIATE](msdn.microsoft.com/en-us/library/cc236650.aspx)

FLAG_KEYS
LAN_MANAGER_HEX_DIGEST_REGEXP

Valid format for LAN Manager hex digest portion: 32 hexadecimal characters.

LM_MAGIC
MAX64
NT_LAN_MANAGER_HEX_DIGEST_REGEXP

Valid format for NT LAN Manager hex digest portion: 32 hexadecimal characters.

SSP_SIGN
TIME_OFFSET

Public Class Methods

apply_des(plain, keys) click to toggle source
# File lib/net/ntlm.rb, line 128
def apply_des(plain, keys)
  keys.map {|k|
    # Spec requires des-cbc, but openssl 3 does not support single des
    # by default, so just do triple DES (EDE) with the same key
    dec = OpenSSL::Cipher.new("des-ede-cbc").encrypt
    dec.padding = 0
    dec.key = k + k
    dec.update(plain) + dec.final
  }
end
gen_keys(str) click to toggle source

Each byte of a DES key contains seven bits of key material and one odd-parity bit. The parity bit should be set so that there are an odd number of 1 bits in each byte. @param [String] str String to generate keys for @api private

# File lib/net/ntlm.rb, line 120
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
is_ntlm_hash?(data) click to toggle source

Takes a string and determines whether it is a valid NTLM Hash @param [String] the string to validate @return [Boolean] whether or not the string is a valid NTLM hash

# File lib/net/ntlm.rb, line 89
def is_ntlm_hash?(data)
  decoded_data = data.dup
  decoded_data = EncodeUtil.decode_utf16le(decoded_data)
  if DATA_REGEXP.match(decoded_data)
    true
  else
    false
  end
end
lm_hash(password) click to toggle source

Generates a {en.wikipedia.org/wiki/LAN_Manager LAN Manager Hash} @param [String] password The password to base the hash on

# File lib/net/ntlm.rb, line 141
def lm_hash(password)
  keys = gen_keys password.upcase.ljust(14, "\0")
  apply_des(LM_MAGIC, keys).join
end
lm_response(arg) click to toggle source
# File lib/net/ntlm.rb, line 187
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 = {}) click to toggle source
# File lib/net/ntlm.rb, line 242
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 = {}) click to toggle source
# File lib/net/ntlm.rb, line 258
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 = {}) click to toggle source

Generate an NTLM Hash @param [String] password The password to base the hash on @option opt :unicode (false) Unicode encode the password

# File lib/net/ntlm.rb, line 149
def ntlm_hash(password, opt = {})
  pwd = password.dup
  unless opt[:unicode]
    pwd = EncodeUtil.encode_utf16le(pwd)
  end
  Net::NTLM::Md4.digest pwd
end
ntlm_response(arg) click to toggle source
# File lib/net/ntlm.rb, line 199
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={}) click to toggle source

Generate a NTLMv2 Hash @param [String] user The username @param [String] password The password @param [String] target The domain or workstation to authenticate to @option [Boolean] opt :unicode (false) Unicode encode the domain.

# File lib/net/ntlm.rb, line 162
def ntlmv2_hash(user, password, target, opt={})
  if is_ntlm_hash? password
    decoded_password = EncodeUtil.decode_utf16le(password)
    ntlmhash = [decoded_password.upcase[33,65]].pack('H32')
  else
    ntlmhash = ntlm_hash(password, opt)
  end

  if opt[:unicode]
    # Uppercase operation on username containing non-ASCI characters
    # after behing unicode encoded with `EncodeUtil.encode_utf16le`
    # doesn't play well. Upcase should be done before encoding.
    user_upcase = EncodeUtil.decode_utf16le(user).upcase
    user_upcase = EncodeUtil.encode_utf16le(user_upcase)
  else
    user_upcase = user.upcase
  end
  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 = {}) click to toggle source
# File lib/net/ntlm.rb, line 207
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) click to toggle source

Convert the value to a 64-bit little-endian integer @param [String] val The string to convert

# File lib/net/ntlm.rb, line 101
def pack_int64le(val)
  [val & 0x00000000ffffffff, val >> 32].pack("V2")
end
split7(str) click to toggle source

Builds an array of strings that are 7 characters long @param [String] str The string to split @api private

# File lib/net/ntlm.rb, line 108
def split7(str)
  s = str.dup
  until s.empty?
    (ret ||= []).push s.slice!(0, 7)
  end
  ret
end