Class: Rex::Proto::Kerberos::Crypto::Rc4Hmac
- Inherits:
-
Object
- Object
- Rex::Proto::Kerberos::Crypto::Rc4Hmac
- Defined in:
- lib/rex/proto/kerberos/crypto/rc4_hmac.rb
Constant Summary collapse
- MAC_SIZE =
16
- CONFOUNDER_SIZE =
8
- PADDING_SIZE =
1
Instance Method Summary collapse
- #calculate_encrypted_length(plaintext_len) ⇒ Object
-
#checksum(key, msg_type, data) ⇒ String
Use this class’s encryption routines to create a checksum of the data based on the key and message type.
-
#decrypt(ciphertext, key, msg_type) ⇒ String
(also: #decrypt_asn1)
Decrypts the cipher using RC4-HMAC schema datatracker.ietf.org/doc/rfc4757/.
-
#encrypt(plaintext, key, msg_type, confounder: nil) ⇒ String
Encrypts the cipher using RC4-HMAC schema datatracker.ietf.org/doc/rfc4757/.
- #gss_unwrap(ciphertext, key, expected_sequence_number, is_initiator, opts = {}) ⇒ Object
- #gss_wrap(plaintext, key, sequence_number, is_initiator, opts = {}) ⇒ Object
-
#header_byte_count ⇒ Object
The number of bytes in the encrypted plaintext that precede the actual plaintext.
-
#string_to_key(password, salt = nil, params: nil) ⇒ String
Derive an encryption key based on a password and salt for the given cipher type.
-
#trailing_byte_count ⇒ Object
The number of bytes in the encrypted plaintext that follow the actual plaintext.
Methods included from Gss::Asn1
#unwrap_pseudo_asn1, #wrap_pseudo_asn1
Methods included from Utils
Instance Method Details
#calculate_encrypted_length(plaintext_len) ⇒ Object
270 271 272 273 |
# File 'lib/rex/proto/kerberos/crypto/rc4_hmac.rb', line 270 def calculate_encrypted_length(plaintext_len) # We add 1-8 bytes of padding, per RFC1964 section 1.2.2.3 plaintext_len + (8 - (plaintext_len % 8)) end |
#checksum(key, msg_type, data) ⇒ String
Use this class’s encryption routines to create a checksum of the data based on the key and message type
35 36 37 38 39 40 |
# File 'lib/rex/proto/kerberos/crypto/rc4_hmac.rb', line 35 def checksum(key, msg_type, data) ksign = OpenSSL::HMAC.digest('MD5', key, "signaturekey\x00") md5_hash = Rex::Text.md5_raw(usage_str(msg_type) + data) ksign = OpenSSL::HMAC.digest('MD5', ksign, md5_hash) end |
#decrypt(ciphertext, key, msg_type) ⇒ String Also known as: decrypt_asn1
Decrypts the cipher using RC4-HMAC schema datatracker.ietf.org/doc/rfc4757/
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 |
# File 'lib/rex/proto/kerberos/crypto/rc4_hmac.rb', line 50 def decrypt(ciphertext, key, msg_type) unless ciphertext && ciphertext.length > MAC_SIZE raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'RC4-HMAC decryption failed' end checksum = ciphertext[0, MAC_SIZE] data = ciphertext[MAC_SIZE, ciphertext.length - 1] k1 = OpenSSL::HMAC.digest('MD5', key, usage_str(msg_type)) k3 = OpenSSL::HMAC.digest('MD5', k1, checksum) cipher = OpenSSL::Cipher.new('rc4') cipher.decrypt cipher.key = k3 decrypted = cipher.update(data) + cipher.final if OpenSSL::HMAC.digest('MD5', k1, decrypted) != checksum raise ::Rex::Proto::Kerberos::Model::Error::KerberosError, 'RC4-HMAC decryption failed, incorrect checksum verification' end # Expect the first CONFOUNDER_SIZE bytes to be the confounder raise ::Rex::Proto::Kerberos::Model::Error::KerberosDecodingError, 'EncryptedData failed to decrypt' if decrypted.length < CONFOUNDER_SIZE # Skip the confounder when returning decrypted[CONFOUNDER_SIZE,decrypted.length] end |
#encrypt(plaintext, key, msg_type, confounder: nil) ⇒ String
Encrypts the cipher using RC4-HMAC schema datatracker.ietf.org/doc/rfc4757/
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/rex/proto/kerberos/crypto/rc4_hmac.rb', line 87 def encrypt(plaintext, key, msg_type, confounder: nil) k1 = OpenSSL::HMAC.digest('MD5', key, usage_str(msg_type)) confounder = Random.urandom(CONFOUNDER_SIZE) if confounder == nil data_encrypt = confounder + plaintext checksum = OpenSSL::HMAC.digest('MD5', k1, data_encrypt) k3 = OpenSSL::HMAC.digest('MD5', k1, checksum) cipher = OpenSSL::Cipher.new('rc4') cipher.encrypt cipher.key = k3 encrypted = cipher.update(data_encrypt) + cipher.final res = checksum + encrypted res end |
#gss_unwrap(ciphertext, key, expected_sequence_number, is_initiator, opts = {}) ⇒ Object
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/rex/proto/kerberos/crypto/rc4_hmac.rb', line 106 def gss_unwrap(ciphertext, key, expected_sequence_number, is_initiator, opts={}) # Always 32-bit sequence number expected_sequence_number &= 0xFFFFFFFF unless expected_sequence_number.nil? mech_id, ciphertext = unwrap_pseudo_asn1(ciphertext) raise Rex::Proto::Kerberos::Model::Error::KerberosError unless ciphertext.length > 0x20 header = ciphertext[0,8] tok_id, alg, seal_alg, filler = header.unpack('nnnn') raise Rex::Proto::Kerberos::Model::Error::KerberosError, "Invalid token id: #{tok_id}" unless tok_id == 0x0201 raise Rex::Proto::Kerberos::Model::Error::KerberosError, "Invalid alg: #{alg}" unless alg == 0x1100 raise Rex::Proto::Kerberos::Model::Error::KerberosError, "Invalid seal_alg: #{seal_alg}" unless seal_alg == 0x1000 raise Rex::Proto::Kerberos::Model::Error::KerberosError, "Invalid filler: #{filler}" unless filler == 0xFFFF encrypted_sequence_num = ciphertext[8,8] eight_checksum_bytes = ciphertext[16,8] encrypted_confounder = ciphertext[24,8] = ciphertext[32, ciphertext.length - 32] kseq = OpenSSL::HMAC.digest('MD5', key.value, [0].pack('V')) kseq = OpenSSL::HMAC.digest('MD5', kseq, eight_checksum_bytes) cipher_seq = OpenSSL::Cipher.new('rc4') cipher_seq.decrypt cipher_seq.key = kseq decrypted_sequence_num = cipher_seq.update(encrypted_sequence_num) decrypted_sequence_num = decrypted_sequence_num.unpack('N')[0] #raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'Invalid sequence number' unless (decrypted_sequence_num == expected_sequence_number || expected_sequence_number.nil?) klocal = xor_strings(key.value, "\xF0"*16) kcrypt = OpenSSL::HMAC.digest('MD5', klocal, [0].pack('V')) # Salt it with the sequence number kcrypt = OpenSSL::HMAC.digest('MD5', kcrypt, [decrypted_sequence_num].pack('N')) cipher = OpenSSL::Cipher.new('rc4') cipher.encrypt cipher.key = kcrypt decrypted_confounder = cipher.update(encrypted_confounder) plaintext = cipher.update() chksum_input = usage_str(Rex::Proto::Kerberos::Crypto::KeyUsage::KRB_PRIV_ENCPART) + header + decrypted_confounder ksign = OpenSSL::HMAC.digest('MD5', key.value, "signaturekey\x00") sgn_cksum = Rex::Text.md5_raw(chksum_input+plaintext) sgn_cksum = OpenSSL::HMAC.digest('MD5', ksign, sgn_cksum) verification_eight_checksum_bytes = sgn_cksum[0,8] raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'Checksum error' unless verification_eight_checksum_bytes == eight_checksum_bytes # Remove padding, if present (seems MS may not send it back?) pad_char = plaintext[-1].ord if 1 <= pad_char && pad_char <= 8 plaintext = plaintext[0, plaintext.length-pad_char] end plaintext end |
#gss_wrap(plaintext, key, sequence_number, is_initiator, opts = {}) ⇒ Object
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 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 |
# File 'lib/rex/proto/kerberos/crypto/rc4_hmac.rb', line 170 def gss_wrap(plaintext, key, sequence_number, is_initiator, opts={}) dce_style = opts.fetch(:dce_style) { false } pad_style = opts.fetch(:rc4_pad_style) { :single_byte } # Always 32-bit sequence number sequence_number &= 0xFFFFFFFF # Header tok_id = 0x0201 alg = 0x1100 seal_alg = 0x1000 filler = 0xFFFF header = [tok_id, alg, seal_alg, filler].pack('nnnn') # Add padding (see RFC1964 section 1.2.2.3) # Some protocols (LDAP) only support a single byte and seem to fail otherwise # Others (DRSR) only support 8-byte and seem to fail otherwise # Some (WinRM) are lenient and are fine with either # # It's not entirely clear why if pad_style == :single_byte pad_num = 1 elsif pad_style == :eight_byte_aligned pad_num = (8 - (plaintext.length % 8)) else raise ArgumentError.new('Unknown pad_style setting') end plaintext += (pad_num.chr * pad_num) send_seq = [sequence_number].pack('N') # See errata on RFC4757 initiator_bytes = "\xFF" * 4 initiator_bytes = "\x00" * 4 if is_initiator send_seq += initiator_bytes confounder = Random.urandom(CONFOUNDER_SIZE) chksum_input = usage_str(Rex::Proto::Kerberos::Crypto::KeyUsage::KRB_PRIV_ENCPART) + header + confounder ksign = OpenSSL::HMAC.digest('MD5', key.value, "signaturekey\x00") sgn_cksum = Rex::Text.md5_raw(chksum_input+plaintext) klocal = xor_strings(key.value, "\xF0"*16) kcrypt = OpenSSL::HMAC.digest('MD5', klocal, [0].pack('V')) # Salt it with the sequence number kcrypt = OpenSSL::HMAC.digest('MD5', kcrypt, [sequence_number].pack('N')) cipher = OpenSSL::Cipher.new('rc4') cipher.encrypt cipher.key = kcrypt encrypted_confounder = cipher.update(confounder) encrypted = cipher.update(plaintext) sgn_cksum = OpenSSL::HMAC.digest('MD5', ksign, sgn_cksum) eight_checksum_bytes = sgn_cksum[0,8] kseq = OpenSSL::HMAC.digest('MD5', key.value, [0].pack('V')) kseq = OpenSSL::HMAC.digest('MD5', kseq, eight_checksum_bytes) cipher_seq = OpenSSL::Cipher.new('rc4') cipher_seq.encrypt cipher_seq.key = kseq encrypted_sequence_num = cipher_seq.update(send_seq) token = header + encrypted_sequence_num + eight_checksum_bytes + encrypted_confounder size_prior = (token+encrypted).length if dce_style wrapped_token = wrap_pseudo_asn1( ::Rex::Proto::Gss::OID_KERBEROS_5, token ) + encrypted else wrapped_token = wrap_pseudo_asn1( ::Rex::Proto::Gss::OID_KERBEROS_5, token + encrypted ) end asn1_length = wrapped_token.length - size_prior token_length = asn1_length + token.length [wrapped_token, token_length, pad_num] end |
#header_byte_count ⇒ Object
The number of bytes in the encrypted plaintext that precede the actual plaintext
259 260 261 |
# File 'lib/rex/proto/kerberos/crypto/rc4_hmac.rb', line 259 def header_byte_count MAC_SIZE + CONFOUNDER_SIZE end |
#string_to_key(password, salt = nil, params: nil) ⇒ String
Derive an encryption key based on a password and salt for the given cipher type
22 23 24 25 26 27 |
# File 'lib/rex/proto/kerberos/crypto/rc4_hmac.rb', line 22 def string_to_key(password, salt=nil, params: nil) raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'Params not supported for RC4_HMAC' unless params == nil unicode_password = password.encode('utf-16le') password_digest = OpenSSL::Digest.digest('MD4', unicode_password) end |
#trailing_byte_count ⇒ Object
The number of bytes in the encrypted plaintext that follow the actual plaintext
266 267 268 |
# File 'lib/rex/proto/kerberos/crypto/rc4_hmac.rb', line 266 def trailing_byte_count 0 end |