Class: Rex::Proto::Kerberos::Crypto::AesBlockCipherBase

Inherits:
BlockCipherBase show all
Includes:
GssNewEncryptionType, Utils
Defined in:
lib/rex/proto/kerberos/crypto/aes_block_cipher_base.rb

Overview

Base class for RFC3962 AES encryption classes

Direct Known Subclasses

Aes128CtsSha1, Aes256CtsSha1

Constant Summary collapse

BLOCK_SIZE =
16
PADDING_SIZE =
1
MAC_SIZE =
12
HASH_FUNCTION =
'SHA1'

Constants included from GssNewEncryptionType

GssNewEncryptionType::GSS_ACCEPTOR_SUBKEY, GssNewEncryptionType::GSS_HEADER_LEN, GssNewEncryptionType::GSS_SEALED, GssNewEncryptionType::GSS_SENT_BY_ACCEPTOR, GssNewEncryptionType::TOK_ID_GSS_WRAP

Instance Method Summary collapse

Methods included from GssNewEncryptionType

#calculate_encrypted_length, #gss_unwrap, #gss_wrap

Methods included from Utils

#xor_bytes, #xor_strings

Methods inherited from BlockCipherBase

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

Instance Method Details

#decrypt_basic(ciphertext, key) ⇒ Object



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/rex/proto/kerberos/crypto/aes_block_cipher_base.rb', line 59

def decrypt_basic(ciphertext, key)
  raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'Ciphertext too short' if ciphertext.length < BLOCK_SIZE

  cipher = OpenSSL::Cipher.new(self.class::DECRYPT_CIPHER_NAME)
  if key.length != cipher.key_len
    raise Rex::Proto::Kerberos::Model::Error::KerberosError, "Decryption key length must be #{cipher.key_len} for #{self.class::ENCRYPT_CIPHER_NAME}"
  end

  cipher.decrypt
  cipher.key = key
  cipher.padding = 0

  if ciphertext.length == BLOCK_SIZE
    return cipher.update(ciphertext) + cipher.final
  end

  # Split the ciphertext into blocks.  The last block may be partial.
  block_chunks = ciphertext.unpack('C*').each_slice(BLOCK_SIZE).to_a
  last_block_length = block_chunks[-1].length

  # CBC-decrypt all but the last two blocks.
  prev_chunk = [0] * BLOCK_SIZE
  plaintext_arr = []
  block_chunks.slice(0..-3).each do |chunk|
    decrypted = cipher.update(chunk.pack('C*')) + cipher.final
    decrypted_arr = decrypted.unpack('C*')
    plaintext_arr += xor_bytes(decrypted_arr, prev_chunk)
    prev_chunk = chunk
  end

  # Decrypt the second-to-last cipher block.  The left side of
  # the decrypted block will be the final block of plaintext
  # xor'd with the final partial cipher block; the right side
  # will be the omitted bytes of ciphertext from the final
  # block.
  decrypted = cipher.update(block_chunks[-2].pack('C*')) + cipher.final
  decrypted_arr = decrypted.unpack('C*')
  last_plaintext_arr = xor_bytes(decrypted_arr[0, last_block_length], block_chunks[-1])
  omitted_arr = decrypted_arr[last_block_length, decrypted.length]

  # Decrypt the final cipher block plus the omitted bytes to get
  # the second-to-last plaintext block.

  decrypted = cipher.update((block_chunks[-1] + omitted_arr).pack('C*'))
  decrypted_arr = decrypted.unpack('C*')
  plaintext_arr += xor_bytes(decrypted_arr, prev_chunk)
  (plaintext_arr + last_plaintext_arr).pack('C*')
end

#encrypt_basic(plaintext, key) ⇒ Object



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/rex/proto/kerberos/crypto/aes_block_cipher_base.rb', line 34

def encrypt_basic(plaintext, key)
  raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'Ciphertext too short' if plaintext.length < BLOCK_SIZE

  cipher = OpenSSL::Cipher.new(self.class::ENCRYPT_CIPHER_NAME)
  if key.length != cipher.key_len
    raise Rex::Proto::Kerberos::Model::Error::KerberosError, "Encryption key length must be #{cipher.key_len} for #{self.class::ENCRYPT_CIPHER_NAME}"
  end

  cipher.encrypt
  cipher.key = key
  cipher.padding = 0

  padded = pad_with_zeroes(plaintext, BLOCK_SIZE)
  ciphertext = cipher.update(padded) + cipher.final
  if plaintext.length > BLOCK_SIZE
    # Swap the last two ciphertext blocks and truncate the
    # final block to match the plaintext length.
    last_block_length = plaintext.length % BLOCK_SIZE
    last_block_length = BLOCK_SIZE if last_block_length == 0
    ciphertext = ciphertext[0, ciphertext.length - 32] + ciphertext[-BLOCK_SIZE, BLOCK_SIZE] + ciphertext[-32, last_block_length]
  end

  ciphertext
end

#header_byte_countObject

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



111
112
113
# File 'lib/rex/proto/kerberos/crypto/aes_block_cipher_base.rb', line 111

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)

    When unpacked, the number of iterations used during key generation

Returns:

  • (String)

    The derived key



26
27
28
29
30
31
32
# File 'lib/rex/proto/kerberos/crypto/aes_block_cipher_base.rb', line 26

def string_to_key(password, salt, params: nil)
  params = "\x00\x00\x10\x00" if params == nil
  iterations = params.unpack('N')[0]
  seed = OpenSSL::KDF.pbkdf2_hmac(password, salt: salt, iterations: iterations, length: self.class::SEED_SIZE, hash: HASH_FUNCTION)
  tkey = random_to_key(seed)
  derive(tkey, 'kerberos'.encode('utf-8'))
end

#trailing_byte_countObject

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



118
119
120
# File 'lib/rex/proto/kerberos/crypto/aes_block_cipher_base.rb', line 118

def trailing_byte_count
  MAC_SIZE
end