CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/rex/proto/kerberos/crypto/des_cbc_md5.rb
Views: 11766
1
# -*- coding: binary -*-
2
3
require 'rex/text'
4
5
module Rex
6
module Proto
7
module Kerberos
8
module Crypto
9
class DesCbcMd5 < BlockCipherBase
10
include Rex::Proto::Kerberos::Crypto::Asn1Utils
11
12
HASH_LENGTH = 16
13
BLOCK_SIZE = 8
14
PADDING_SIZE = 8
15
MAC_SIZE = 16
16
17
# Derive an encryption key based on a password and salt for the given cipher type
18
#
19
# @param password [String] The password to use as the basis for key generation
20
# @param salt [String] A salt (usually based on domain and username)
21
# @param params [String] Unused for this encryption type
22
# @return [String] The derived key
23
def string_to_key(password, salt, params: nil)
24
raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'Params not supported for DES' unless params == nil
25
reverse_this_block = false
26
tempstring = [0,0,0,0,0,0,0,0]
27
28
utf8_encoded = (password + salt).encode('UTF-8').bytes.pack('C*')
29
30
data = pad_with_zeroes(utf8_encoded, PADDING_SIZE)
31
data_as_blocks = data.unpack('C*')
32
33
data_as_blocks.each_slice(BLOCK_SIZE) do |block|
34
result = []
35
block.each do |byte|
36
# Ignore the Most Significant Bit of each byte
37
result.append(byte & 0x7F)
38
end
39
40
if reverse_this_block
41
reversed = []
42
result.reverse.each do |byte|
43
d = byte.digits(2)
44
d = d + [0] * (7 - d.length)
45
reversed.append(d.join('').to_i(2))
46
end
47
48
result = reversed
49
end
50
51
reverse_this_block = (not reverse_this_block)
52
53
tempstring = xor_bytes(tempstring,result)
54
end
55
56
paritied = addparity(tempstring)
57
tempkey = paritied.pack('C*')
58
59
if _is_weak_des_key(tempkey)
60
paritied[7] = paritied[7] ^ 0xF0
61
tempkey = paritied.pack('C*')
62
end
63
64
cipher = OpenSSL::Cipher.new('des-cbc')
65
cipher.encrypt
66
cipher.padding = 0
67
cipher.key = tempkey
68
cipher.iv = tempkey
69
70
encrypted = cipher.update(data) + cipher.final
71
checksumkey = encrypted
72
73
checksumkey = encrypted[-8,8]
74
paritied = fixparity(checksumkey.unpack('C*'))
75
checksumkey = paritied.pack('C*')
76
if _is_weak_des_key(checksumkey)
77
paritied[7] = paritied[7] ^ 0xF0
78
checksumkey = paritied.pack('C*')
79
end
80
81
checksumkey
82
end
83
84
# Decrypts the cipher using DES-CBC-MD5 schema
85
#
86
# @param ciphertext [String] the data to decrypt
87
# @param key [String] the key to decrypt
88
# @param msg_type [Integer] ignored for this algorithm
89
# @return [String] the decrypted cipher
90
# @raise [Rex::Proto::Kerberos::Model::Error::KerberosError] if decryption doesn't succeed
91
def decrypt(ciphertext, key, msg_type)
92
raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'Ciphertext too short' unless ciphertext && ciphertext.length > BLOCK_SIZE + HASH_LENGTH
93
raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'Ciphertext is not a multiple of block length' unless ciphertext.length % BLOCK_SIZE == 0
94
95
96
cipher = OpenSSL::Cipher.new('des-cbc')
97
if key.length != cipher.key_len
98
raise Rex::Proto::Kerberos::Model::Error::KerberosError, "Decryption key length must be #{cipher.key_len} for des-cbc"
99
end
100
cipher.decrypt
101
cipher.padding = 0
102
cipher.key = key
103
decrypted = cipher.update(ciphertext)
104
105
confounder = decrypted[0, BLOCK_SIZE]
106
checksum = decrypted[BLOCK_SIZE, HASH_LENGTH]
107
plaintext = decrypted[BLOCK_SIZE + HASH_LENGTH, decrypted.length]
108
hashed_data = confounder + "\x00" * HASH_LENGTH + plaintext
109
110
hash_fn = OpenSSL::Digest.new('MD5')
111
112
if hash_fn.digest(hashed_data) != checksum
113
raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'HMAC integrity error'
114
end
115
116
plaintext
117
end
118
119
def decrypt_asn1(ciphertext, key, msg_type)
120
result = decrypt(ciphertext, key, msg_type)
121
padding_removed = truncate_nulls_after_asn1(result)
122
end
123
124
# Encrypts the cipher using DES-CBC-MD5 schema
125
#
126
# @param plaintext [String] the data to encrypt
127
# @param key [String] the key to encrypt
128
# @param msg_type [Integer] ignored for this algorithm
129
# @param confounder [String] Optionally force the confounder to a specific value
130
# @return [String] the encrypted data
131
def encrypt(plaintext, key, msg_type, confounder: nil)
132
confounder = Rex::Text::rand_text(BLOCK_SIZE) if confounder == nil
133
padded_data = pad_with_zeroes(plaintext, PADDING_SIZE)
134
hashed_data = confounder + "\x00" * HASH_LENGTH + padded_data
135
hash_fn = OpenSSL::Digest.new('MD5')
136
checksum = hash_fn.digest(hashed_data)
137
138
raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'Invalid checksum size' unless checksum.length == HASH_LENGTH
139
140
plaintext = confounder + checksum + padded_data
141
142
cipher = OpenSSL::Cipher.new('des-cbc')
143
if key.length != cipher.key_len
144
raise Rex::Proto::Kerberos::Model::Error::KerberosError, "Encryption key length must be #{cipher.key_len} for des-cbc"
145
end
146
cipher.encrypt
147
cipher.padding = 0
148
cipher.key = key
149
encrypted = cipher.update(plaintext) + cipher.final
150
151
encrypted
152
end
153
154
#
155
# The number of bytes in the encrypted plaintext that precede the actual plaintext
156
#
157
def header_byte_count
158
BLOCK_SIZE
159
end
160
161
#
162
# The number of bytes in the encrypted plaintext that follow the actual plaintext
163
#
164
def trailing_byte_count
165
MAC_SIZE
166
end
167
168
private
169
170
def fixparity(deskey)
171
temp = []
172
deskey.each do |byte|
173
bits = byte.digits(2).reverse
174
bits.pop # Ignore the last bit, since it's a parity bit
175
add_at_end = (bits.count(1) + 1) % 2
176
bits.append(add_at_end)
177
parity_fixed = bits.join('').to_i(2)
178
temp.append(parity_fixed)
179
end
180
181
temp
182
end
183
184
def addparity(bytes)
185
temp = []
186
bytes.each do |byte|
187
bits = byte.digits(2).reverse
188
to_add = (bits.count(1) + 1) % 2
189
result = (byte << 1) + to_add
190
temp.append(result & 0xFF)
191
end
192
193
temp
194
end
195
196
def xor_bytes(l1,l2)
197
result = []
198
l1.zip(l2).each do |b1,b2|
199
if b1 != nil && b2 != nil
200
result.append((b1^b2)&0b01111111)
201
end
202
end
203
204
result
205
end
206
end
207
end
208
end
209
end
210
end
211
212