Skip to content

Commit b02d6e8

Browse files
committed
Fixed python3 migration multiple errors
1 parent d3ea82b commit b02d6e8

11 files changed

+162
-107
lines changed

pysap/SAPCredv2.py

+20-15
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ def decrypt_MSCryptProtect(plain, cred):
9898
:rtype: string
9999
"""
100100
entropy = cred.pse_path
101-
return dpapi_decrypt_blob(unhexlify(plain.blob.val), entropy)
101+
return dpapi_decrypt_blob(unhexlify(plain.blob.val).decode(), entropy)
102102

103103
PROVIDER_MSCryptProtect = "MSCryptProtect"
104104
"""Provider for Windows hosts using DPAPI"""
@@ -170,14 +170,14 @@ def lps_type_str(self):
170170
@property
171171
def cipher_format_version(self):
172172
cipher = self.cipher.val_readable
173-
if len(cipher) >= 36 and ord(cipher[0]) in [0, 1]:
174-
return ord(cipher[0])
173+
if len(cipher) >= 36 and (cipher[0]) in [0, 1]:
174+
return cipher[0]
175175
return 0
176176

177177
@property
178178
def cipher_algorithm(self):
179179
if self.cipher_format_version == 1:
180-
return ord(self.cipher.val_readable[1])
180+
return self.cipher.val_readable[1]
181181
return 0
182182

183183
def decrypt(self, username):
@@ -216,34 +216,39 @@ def decrypt_simple(self, username):
216216
iv = "\x00" * 8
217217

218218
# Decrypt the cipher text with the derived key and IV
219-
decryptor = Cipher(algorithms.TripleDES(key), modes.CBC(iv), backend=default_backend()).decryptor()
219+
decryptor = Cipher(algorithms.TripleDES(key.encode()), modes.CBC(iv.encode()), backend=default_backend()).decryptor()
220220
plain = decryptor.update(blob) + decryptor.finalize()
221221

222222
return SAPCredv2_Cred_Plain(plain)
223223

224-
def xor(self, string, start):
225-
"""XOR a given string using a fixed key and a starting number."""
224+
def xor(self, data, start):
225+
"""XOR given data using a fixed key and a starting number."""
226226
key = 0x15a4e35
227227
x = start
228-
y = ""
229-
for c in string:
228+
y = bytearray()
229+
for value in data:
230230
x *= key
231231
x += 1
232-
y += chr(ord(c) ^ (x & 0xff))
232+
# Ensure the value is treated as an integer
233+
if isinstance(value, str):
234+
value = ord(value)
235+
# Perform the XOR operation
236+
xored_value = value ^ (x & 0xff)
237+
y.append(xored_value)
233238
return y
234239

235240
def derive_key(self, key, blob, header, username):
236241
"""Derive a key using SAP's algorithm. The key is derived using SHA256 and xor from an
237242
initial key, a header, salt and username.
238243
"""
239244
digest = Hash(SHA256(), backend=default_backend())
240-
digest.update(key)
245+
digest.update(key.encode())
241246
digest.update(blob[0:4])
242247
digest.update(header.salt)
243-
digest.update(self.xor(username, ord(header.salt[0])))
244-
digest.update("" * 0x20)
248+
digest.update(self.xor(username, (header.salt[0])))
249+
digest.update(b"" * 0x20)
245250
hashed = digest.finalize()
246-
derived_key = self.xor(hashed, ord(header.salt[1]))
251+
derived_key = self.xor(hashed, (header.salt[1]))
247252

248253
# Validate and select proper algorithm
249254
if header.algorithm == CIPHER_ALGORITHM_3DES:
@@ -377,7 +382,7 @@ def decrypt(self, username=None):
377382
plain = cipher.decrypt()
378383

379384
# Get the pin from the raw data
380-
plain_size = ord(plain[0])
385+
plain_size = (plain[0])
381386
pin = plain[plain_size + 1:]
382387

383388
# Create a plain credential container

pysap/SAPLPS.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def decrypt(self):
111111

112112
# Decrypt the cipher text with the encryption key
113113
iv = "\x00" * 16
114-
decryptor = Cipher(algorithms.AES(encryption_key), modes.CBC(iv)).decryptor()
114+
decryptor = Cipher(algorithms.AES(encryption_key), modes.CBC(iv.encode())).decryptor()
115115
plain = decryptor.update(self.encrypted_data) + decryptor.finalize()
116116

117117
# TODO: Calculate and validate HMAC
@@ -141,15 +141,15 @@ def decrypt_encryption_key_fallback(self):
141141
log_lps.debug("Obtaining encryption key with FALLBACK LPS mode")
142142

143143
digest = Hash(SHA1())
144-
digest.update(cred_key_lps_fallback)
144+
digest.update(cred_key_lps_fallback.encode())
145145
hashed_key = digest.finalize()
146146

147147
hmac = HMAC(hashed_key, SHA1())
148148
hmac.update(self.context)
149149
default_key = hmac.finalize()[:16]
150150

151151
iv = "\x00" * 16
152-
decryptor = Cipher(algorithms.AES(default_key), modes.CBC(iv)).decryptor()
152+
decryptor = Cipher(algorithms.AES(default_key), modes.CBC(iv.encode())).decryptor()
153153
encryption_key = decryptor.update(self.encrypted_key) + decryptor.finalize()
154154

155155
return encryption_key

pysap/SAPPSE.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ def decrypt_non_lps(self, pin):
334334
# On version 2, we can check that the PIN was valid before decrypting the whole
335335
# cipher text
336336
if self.version == 2:
337-
encrypted_pin = pbes.encrypt(pin)
337+
encrypted_pin = pbes.encrypt(pin.encode())
338338
if encrypted_pin != self.enc_cont.encrypted_pin.val:
339339
raise ValueError("Invalid PIN supplied")
340340

pysap/SAPRouter.py

+9-8
Original file line numberDiff line numberDiff line change
@@ -182,14 +182,15 @@ def from_hops(cls, route_hops):
182182
:return: route string
183183
:rtype: C{string}
184184
"""
185-
result = ""
186-
for route_hop in route_hops:
187-
result += "/H/{}".format(route_hop.hostname)
188-
if route_hop.port:
189-
result += "/S/{}".format(route_hop.port)
190-
if route_hop.password:
191-
result += "/W/{}".format(route_hop.password)
192-
return result
185+
route_string = ""
186+
for hop in route_hops:
187+
if hop.hostname:
188+
route_string += f"/H/{hop.hostname}"
189+
if hop.port:
190+
route_string += f"/S/{hop.port}"
191+
if hop.password:
192+
route_string += f"/W/{hop.password}"
193+
return route_string
193194

194195

195196
class SAPRouterInfoClient(PacketNoPadded):

pysap/SAPSSFS.py

+10-8
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,12 @@ def valid(self):
101101
blob = str(self)
102102

103103
digest = Hash(SHA1())
104-
digest.update(blob[:8])
105-
digest.update(blob[8:8+4])
104+
digest.update(blob[:8].encode())
105+
digest.update(blob[8:8+4].encode())
106106
if self.length:
107-
digest.update(blob[0x20:0x20 + self.length])
107+
digest.update(blob[0x20:0x20 + self.length].encode())
108108
if len(blob) > self.length + 0x20:
109-
digest.update(blob[0x20 + self.length:])
109+
digest.update(blob[0x20 + self.length:].encode())
110110
blob_hash = digest.finalize()
111111
return blob_hash == self.hash
112112

@@ -158,8 +158,8 @@ def valid(self):
158158
"""Returns whether the HMAC-SHA1 value is valid for the given payload"""
159159

160160
# Calculate the HMAC-SHA1
161-
h = HMAC(ssfs_hmac_key_unobscured, SHA1())
162-
h.update(str(self)[24:156]) # Entire Data header without the HMAC field
161+
h = HMAC(ssfs_hmac_key_unobscured.encode(), SHA1())
162+
h.update((self)[24:156]) # Entire Data header without the HMAC field
163163
h.update(self.data)
164164

165165
# Validate the signature
@@ -194,8 +194,9 @@ def has_record(self, key_name):
194194
:return: if the data file contains the record with key_name
195195
:rtype: bool
196196
"""
197+
key_name_bytes = key_name.encode('utf-8') # Convert input string to bytes
197198
for record in self.records:
198-
if record.key_name.rstrip(" ") == key_name:
199+
if record.key_name.rstrip(b" ") == key_name_bytes: # Use b" " for byte string
199200
return True
200201
return False
201202

@@ -208,8 +209,9 @@ def get_records(self, key_name):
208209
:return: the record with key_name
209210
:rtype: SAPSSFSDataRecord
210211
"""
212+
key_name_bytes = key_name.encode('utf-8') # Convert input string to bytes
211213
for record in self.records:
212-
if record.key_name.rstrip(" ") == key_name:
214+
if record.key_name.rstrip(b" ") == key_name_bytes:
213215
yield record
214216

215217
def get_record(self, key_name):

pysap/utils/crypto/__init__.py

+19-22
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def derive(self, password):
111111
v = self._algorithm.block_size
112112

113113
# Step 1 - Concatenate v/8 copies of ID
114-
d = chr(self._id) * v
114+
d = bytes([self._id]) * v
115115

116116
def concatenate_string(inp):
117117
s = b''
@@ -135,7 +135,6 @@ def concatenate_string(inp):
135135
c = int(math.ceil(float(self._length) / u))
136136

137137
# Step 6
138-
139138
def digest(inp):
140139
h = Hash(self._algorithm())
141140
h.update(inp)
@@ -144,17 +143,17 @@ def digest(inp):
144143
def to_int(value):
145144
if value == b'':
146145
return 0
147-
return int(value.encode("hex"), 16)
146+
return int.from_bytes(value, byteorder='big')
148147

149-
def to_bytes(value):
150-
value = "%x" % value
151-
if len(value) & 1:
152-
value = "0" + value
153-
return value.decode("hex")
148+
def to_bytes(value, length):
149+
try:
150+
return value.to_bytes(length, byteorder='big')
151+
except OverflowError:
152+
# If the integer is too large, we'll take the least significant bytes
153+
return (value & ((1 << (8 * length)) - 1)).to_bytes(length, byteorder='big')
154154

155155
a = b'\x00' * (c * u)
156156
for n in range(1, c + 1):
157-
158157
a2 = digest(d + i)
159158
for _ in range(2, self._iterations + 1):
160159
a2 = digest(a2)
@@ -172,13 +171,9 @@ def to_bytes(value):
172171
start = n2 * v
173172
end = (n2 + 1) * v
174173
i_n2 = i[start:end]
175-
i_n2 = to_bytes(to_int(i_n2) + b)
176-
177-
i_n2_l = len(i_n2)
178-
if i_n2_l > v:
179-
i_n2 = i_n2[i_n2_l - v:]
174+
i_n2 = to_bytes(to_int(i_n2) + b, v)
180175

181-
i = i[0:start] + i_n2 + i[end:]
176+
i = i[:start] + i_n2 + i[end:]
182177

183178
# Step 7
184179
start = (n - 1) * u
@@ -230,6 +225,8 @@ def __init__(self, salt, iterations, iv, password, hash_algorithm, enc_algorithm
230225
self._derive_key, self._iv = self.derive_key(salt, iterations, password)
231226

232227
def derive_key(self, salt, iterations, password):
228+
if isinstance(password, str):
229+
password = password.encode()
233230
pkcs12_pbkdf1 = PKCS12_PBKDF1(self._hash_algorithm, 24, salt, iterations, 1)
234231
key = pkcs12_pbkdf1.derive(password)
235232

@@ -248,11 +245,11 @@ def encrypt(self, plain_text):
248245
return cipher_text
249246

250247
def decrypt(self, cipher_text):
251-
padder = padding.PKCS7(self._hash_algorithm.block_size).padder()
252-
cipher_text = padder.update(cipher_text) + padder.finalize()
253-
254248
decryptor = Cipher(self._enc_algorithm(self._derive_key), self._enc_mode(self._iv)).decryptor()
255-
plain_text = decryptor.update(cipher_text) + decryptor.finalize()
249+
padded_plain_text = decryptor.update(cipher_text) + decryptor.finalize()
250+
251+
unpadder = padding.PKCS7(self._hash_algorithm.block_size).unpadder()
252+
plain_text = unpadder.update(padded_plain_text) + unpadder.finalize()
256253

257254
return plain_text
258255

@@ -348,8 +345,8 @@ def rsec_decrypt(blob, key):
348345
if len(key) != 24:
349346
raise Exception("Wrong key length")
350347

351-
blob = [ord(i) for i in blob]
352-
key = [ord(i) for i in key]
348+
blob = [i for i in blob]
349+
key = [i for i in key]
353350
key1 = key[0:8]
354351
key2 = key[8:16]
355352
key3 = key[16:24]
@@ -359,4 +356,4 @@ def rsec_decrypt(blob, key):
359356
round_2 = cipher.crypt(RSECCipher.MODE_ENCODE, round_1, key2, len(round_1))
360357
round_3 = cipher.crypt(RSECCipher.MODE_DECODE, round_2, key1, len(round_2))
361358

362-
return ''.join([chr(i) for i in round_3])
359+
return bytes(round_3)

tests/test_crypto.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def test_scram_sha256_scramble_salt(self):
3030
Values are taken from https://github.com/SAP/PyHDB/blob/master/tests/test_auth.py
3131
"""
3232

33-
password = "secret"
33+
password = b"secret"
3434
salt = b"\x80\x96\x4f\xa8\x54\x28\xae\x3a\x81\xac" \
3535
b"\xd3\xe6\x86\xa2\x79\x33"
3636
server_key = b"\x41\x06\x51\x50\x11\x7e\x45\x5f\xec\x2f\x03\xf6" \
@@ -58,7 +58,7 @@ def test_scram_pbkdf2sha256_scramble_salt(self):
5858
Values are taken from https://github.com/SAP/go-hdb/blob/master/internal/protocol/authentication_test.go
5959
"""
6060

61-
password = "Toor1234"
61+
password = b"Toor1234"
6262
rounds = 15000
6363
salt = b"3\xb2\xd5\xd5\\R\xc2(Px\xc5[\xa6C\x17?"
6464
server_key = b" [\xa5\x12\x9eM\x86E\x80\x9dE\xd1/!\xab\xa48\xac\xe5\x00\x99\x03A\x1d\xef\xd2\xba\x86Q \x1d\x89\xef\xa7'\x01\xabuU\x8am&*M+*RF"

tests/test_sapcredv2.py

+23-8
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
class PySAPCredV2Test(unittest.TestCase):
3232

3333
decrypt_username = "username"
34-
decrypt_pin = "1234567890"
34+
decrypt_pin = b"1234567890"
3535
cert_name = "CN=PSEOwner"
3636
common_name = "PSEOwner"
3737
subject_str = "/CN=PSEOwner"
@@ -49,17 +49,32 @@ def validate_credv2_lps_off_fields(self, creds, number, lps_type, cipher_format_
4949
self.assertEqual(len(creds), number)
5050
cred = creds[0].cred
5151

52-
self.assertEqual(cred.common_name, cert_name or self.cert_name)
53-
self.assertEqual(cred.pse_file_path, pse_path or self.pse_path)
52+
# Check if cert_name is None before encoding
53+
cert_name_encoded = cert_name.encode() if cert_name is not None else None
54+
55+
# Check if pse_path is None before encoding
56+
pse_path_encoded = pse_path.encode() if pse_path is not None else None
57+
58+
# Assert common_name (previously cert_name)
59+
self.assertEqual(cred.common_name, cert_name_encoded or self.cert_name.encode())
60+
61+
# Assert pse_file_path (previously pse_path)
62+
self.assertEqual(cred.pse_file_path, pse_path_encoded or self.pse_path.encode())
63+
64+
# These assertions remain unchanged
5465
self.assertEqual(cred.lps_type, lps_type)
5566
self.assertEqual(cred.cipher_format_version, cipher_format_version)
5667
self.assertEqual(cred.cipher_algorithm, cipher_algorithm)
5768

58-
self.assertEqual(cred.cert_name, cert_name or self.cert_name)
59-
self.assertEqual(cred.unknown1, "")
60-
self.assertEqual(cred.pse_path, pse_path or self.pse_path)
61-
self.assertEqual(cred.unknown2, "")
69+
# Assert cert_name (previously common_name)
70+
self.assertEqual(cred.cert_name, cert_name_encoded or self.cert_name.encode())
71+
72+
self.assertEqual(cred.unknown1, b"")
73+
74+
# Assert pse_path (previously pse_file_path)
75+
self.assertEqual(cred.pse_path, pse_path_encoded or self.pse_path.encode())
6276

77+
self.assertEqual(cred.unknown2, b"")
6378
def validate_credv2_plain(self, cred, decrypt_username=None, decrypt_pin=None):
6479
plain = cred.decrypt(decrypt_username or self.decrypt_username)
6580
self.assertEqual(plain.pin.val, decrypt_pin or self.decrypt_pin)
@@ -200,7 +215,7 @@ def test_credv2_lps_on_v2_int_aes256_composed_subject(self):
200215
subject = [
201216
X509_RDN(rdn=[
202217
X509_AttributeTypeAndValue(type=ASN1_OID("2.5.4.6"),
203-
value=ASN1_PRINTABLE_STRING("AR"))]),
218+
value=ASN1_PRINTABLE_STRING(b"AR"))]),
204219
X509_RDN(rdn=[
205220
X509_AttributeTypeAndValue(type=ASN1_OID("2.5.4.3"),
206221
value=ASN1_PRINTABLE_STRING(self.common_name))]),

tests/test_sapdiag.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def test_sapdiag_header_dissection_plain(self):
5555

5656
diag_header_plain = SAPDiag(compress=0)
5757
diag_header_plain.message.append(diag_item)
58-
new_diag_header_plain = SAPDiag(str(diag_header_plain))
58+
new_diag_header_plain = SAPDiag(diag_header_plain)
5959

6060
self.assertEqual(str(diag_header_plain),
6161
str(new_diag_header_plain))
@@ -148,9 +148,9 @@ class SAPDiagItemTest(Packet):
148148
fields_desc = [StrField("strfield", None)]
149149
bind_diagitem(SAPDiagItemTest, "APPL", 0x99, 0xff)
150150

151-
item_string = "strfield"
151+
item_string = b"strfield"
152152
item_value = SAPDiagItemTest(strfield=item_string)
153-
item = SAPDiagItem("\x10\x99\xff" + pack("!H", len(item_string)) + item_string)
153+
item = SAPDiagItem(b"\x10\x99\xff" + pack("!H", len(item_string)) + item_string)
154154

155155
self.assertEqual(item.item_value, item_value)
156156
self.assertEqual(item.item_length, len(item_string))

0 commit comments

Comments
 (0)