Class: Rex::Proto::Kerberos::Crypto::DesCbcMd5

Inherits:
BlockCipherBase show all
Includes:
Asn1Utils
Defined in:
lib/rex/proto/kerberos/crypto/des_cbc_md5.rb

Constant Summary collapse

HASH_LENGTH =
16
BLOCK_SIZE =
8
PADDING_SIZE =
8
MAC_SIZE =
16

Instance Method Summary collapse

Methods included from Asn1Utils

#truncate_nulls_after_asn1

Methods inherited from BlockCipherBase

#add_ones_complement, #calculate_encrypted_length, #checksum, #gss_unwrap, #gss_wrap, #rotate_right

Instance Method Details

#decrypt(ciphertext, key, msg_type) ⇒ String

Decrypts the cipher using DES-CBC-MD5 schema

Parameters:

  • ciphertext (String)

    the data to decrypt

  • key (String)

    the key to decrypt

  • msg_type (Integer)

    ignored for this algorithm

Returns:

  • (String)

    the decrypted cipher

Raises:



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/rex/proto/kerberos/crypto/des_cbc_md5.rb', line 91

def decrypt(ciphertext, key, msg_type)
  raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'Ciphertext too short' unless ciphertext && ciphertext.length > BLOCK_SIZE + HASH_LENGTH
  raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'Ciphertext is not a multiple of block length' unless ciphertext.length % BLOCK_SIZE == 0


  cipher = OpenSSL::Cipher.new('des-cbc')
  cipher.decrypt
  cipher.padding = 0
  cipher.key = key
  decrypted = cipher.update(ciphertext)

  confounder = decrypted[0, BLOCK_SIZE]
  checksum = decrypted[BLOCK_SIZE, HASH_LENGTH]
  plaintext = decrypted[BLOCK_SIZE + HASH_LENGTH, decrypted.length]
  hashed_data = confounder + "\x00" * HASH_LENGTH + plaintext

  hash_fn = OpenSSL::Digest.new('MD5')

  if hash_fn.digest(hashed_data) != checksum
    raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'HMAC integrity error'
  end

  plaintext
end

#decrypt_asn1(ciphertext, key, msg_type) ⇒ Object



116
117
118
119
# File 'lib/rex/proto/kerberos/crypto/des_cbc_md5.rb', line 116

def decrypt_asn1(ciphertext, key, msg_type)
  result = decrypt(ciphertext, key, msg_type)
  padding_removed = truncate_nulls_after_asn1(result)
end

#encrypt(plaintext, key, msg_type, confounder: nil) ⇒ String

Encrypts the cipher using DES-CBC-MD5 schema

Parameters:

  • plaintext (String)

    the data to encrypt

  • key (String)

    the key to encrypt

  • msg_type (Integer)

    ignored for this algorithm

  • confounder (String) (defaults to: nil)

    Optionally force the confounder to a specific value

Returns:

  • (String)

    the encrypted data

Raises:



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/rex/proto/kerberos/crypto/des_cbc_md5.rb', line 128

def encrypt(plaintext, key, msg_type, confounder: nil)
  confounder = Rex::Text::rand_text(BLOCK_SIZE) if confounder == nil
  padded_data = pad_with_zeroes(plaintext, PADDING_SIZE)
  hashed_data = confounder + "\x00" * HASH_LENGTH + padded_data
  hash_fn = OpenSSL::Digest.new('MD5')
  checksum = hash_fn.digest(hashed_data)

  raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'Invalid checksum size' unless checksum.length == HASH_LENGTH

  plaintext = confounder + checksum + padded_data

  cipher = OpenSSL::Cipher.new('des-cbc')
  cipher.encrypt
  cipher.padding = 0
  cipher.key = key
  encrypted = cipher.update(plaintext) + cipher.final

  encrypted
end

#header_byte_countObject

The number of bytes in the encrypted plaintext that precede the actual plaintext



151
152
153
# File 'lib/rex/proto/kerberos/crypto/des_cbc_md5.rb', line 151

def header_byte_count
  BLOCK_SIZE
end

#string_to_key(password, salt, params: nil) ⇒ String

Derive an encryption key based on a password and salt for the given cipher type

Parameters:

  • password (String)

    The password to use as the basis for key generation

  • salt (String)

    A salt (usually based on domain and username)

  • params (String) (defaults to: nil)

    Unused for this encryption type

Returns:

  • (String)

    The derived key

Raises:



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/rex/proto/kerberos/crypto/des_cbc_md5.rb', line 23

def string_to_key(password, salt, params: nil)
  raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'Params not supported for DES' unless params == nil
  reverse_this_block = false
  tempstring = [0,0,0,0,0,0,0,0]

  utf8_encoded = (password + salt).encode('UTF-8').bytes.pack('C*')

  data = pad_with_zeroes(utf8_encoded, PADDING_SIZE)
  data_as_blocks = data.unpack('C*')

  data_as_blocks.each_slice(BLOCK_SIZE) do |block|
    result = []
    block.each do |byte|
      # Ignore the Most Significant Bit of each byte
      result.append(byte & 0x7F)
    end

    if reverse_this_block
      reversed = []
      result.reverse.each do |byte|
        d = byte.digits(2)
        d = d + [0] * (7 - d.length)
        reversed.append(d.join('').to_i(2))
      end

      result = reversed
    end

    reverse_this_block = (not reverse_this_block)

    tempstring = xor_bytes(tempstring,result)
  end

  paritied = addparity(tempstring)
  tempkey = paritied.pack('C*')

  if _is_weak_des_key(tempkey)
    paritied[7] = paritied[7] ^ 0xF0
    tempkey = paritied.pack('C*')
  end

  cipher = OpenSSL::Cipher.new('des-cbc')
  cipher.encrypt
  cipher.padding = 0
  cipher.key = tempkey
  cipher.iv = tempkey

  encrypted = cipher.update(data) + cipher.final
  checksumkey = encrypted

  checksumkey = encrypted[-8,8]
  paritied = fixparity(checksumkey.unpack('C*'))
  checksumkey = paritied.pack('C*')
  if _is_weak_des_key(checksumkey)
    paritied[7] = paritied[7] ^ 0xF0
    checksumkey = paritied.pack('C*')
  end

  checksumkey
end

#trailing_byte_countObject

The number of bytes in the encrypted plaintext that follow the actual plaintext



158
159
160
# File 'lib/rex/proto/kerberos/crypto/des_cbc_md5.rb', line 158

def trailing_byte_count
  MAC_SIZE
end