Skip to content

Commit

Permalink
rsecssfs key decrypt fix (see #70) (#71)
Browse files Browse the repository at this point in the history
* rsecssfs key decrypt fix (see #70)
* adding unit test
  • Loading branch information
gvb84 authored Oct 24, 2023
1 parent 37d79bd commit d930e40
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 2 deletions.
22 changes: 21 additions & 1 deletion pysap/SAPSSFS.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from cryptography.hazmat.primitives.hashes import Hash, SHA1
from cryptography.hazmat.backends import default_backend
# Custom imports
from pysap.utils.crypto import rsec_decrypt
from pysap.utils.crypto import rsec_decrypt, rsec_decrypt_key
from pysap.utils.fields import PacketNoPadded, StrFixedLenPaddedField, TimestampField


Expand Down Expand Up @@ -78,6 +78,26 @@ class SAPSSFSKey(Packet):
StrFixedLenPaddedField("host", None, 24, padd=" "),
]

class SAPSSFSKeyE(Packet):
"""SAP SSFS Key (encrypted) file format packet.
Key file length is 0xbb
"""
name = "SAP SSFS Encrypted Key"
fields_desc = [
StrFixedLenField("preamble", "RSecSSFsKey", 11),
ByteField("type", None),
TimestampField("timestamp", None),
StrFixedLenPaddedField("user", None, 24, padd=" "),
StrFixedLenPaddedField("host", None, 24, padd=" "),
# probably kind of check sum or just noise
StrFixedLenField("unknown", None, 62),
StrFixedLenField("key_enc", None, 57),
]

@property
def key(self):
return rsec_decrypt_key(self.key_enc)

class SAPSSFSDecryptedPayload(PacketNoPadded):
"""SAP SSFS Decrypted Payload.
Expand Down
29 changes: 29 additions & 0 deletions pysap/utils/crypto/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,3 +367,32 @@ def rsec_decrypt(blob, key):
round_3 = cipher.crypt(RSECCipher.MODE_DECODE, round_2, key1, len(round_2))

return ''.join([chr(i) for i in round_3])

def rsec_decrypt_key(key_enc):
kek = "\x9F\x60\xA6\xDD\x7E\x15\x7D\x07\x0C\xC3\x57\x90\x9A\xA2\x90\xE9\x36\x0E\xEE\x47\x2F\xDA\x47\x72"
kek = [ord(i) for i in kek]
kek1 = kek[0:8]
kek2 = kek[8:16]
kek3 = kek[16:24]
""" Default Key Encryption Key embedded in rsecssfx/kernel binaries """

blob = [ord(i) for i in key_enc[:56]]
last_key_byte = bytearray(key_enc[56:])
""" Last key byte is computed outside DES decryption """

cipher = RSECCipher()
round_1 = cipher.crypt(RSECCipher.MODE_DECODE, blob, kek3, len(blob))
round_2 = cipher.crypt(RSECCipher.MODE_ENCODE, round_1, kek2, len(round_1))
round_3 = cipher.crypt(RSECCipher.MODE_DECODE, round_2, kek1, len(round_2))

t1 = [ord(i) for i in key_enc[48:56]]
tmp = cipher.crypt(RSECCipher.MODE_ENCODE, t1, kek3, 8)
last_key_byte = last_key_byte[0] ^ tmp[0]

tmp = cipher.crypt(RSECCipher.MODE_ENCODE, round_2[48:56], kek2, 8)
last_key_byte = last_key_byte ^ tmp[0]

tmp = cipher.crypt(RSECCipher.MODE_ENCODE, round_2[48:56], kek1, 8)
last_key_byte = last_key_byte ^ tmp[0]

return [chr(c) for c in round_3[33:] + [last_key_byte]]
Binary file added tests/data/ssfs_npl_dat
Binary file not shown.
Binary file added tests/data/ssfs_npl_key
Binary file not shown.
26 changes: 25 additions & 1 deletion tests/sapssfs_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
# External imports
# Custom imports
from tests.utils import data_filename
from pysap.SAPSSFS import (SAPSSFSKey, SAPSSFSData, SAPSSFSLock)
from pysap.SAPSSFS import (SAPSSFSKey, SAPSSFSKeyE, SAPSSFSData, SAPSSFSLock)


class PySAPSSFSKeyTest(unittest.TestCase):
Expand Down Expand Up @@ -145,6 +145,30 @@ def test_ssfs_data_record_decrypt(self):
self.assertFalse(record.is_stored_as_plaintext)
self.assertTrue(record.valid)

class PySAPSSFSDataDecryptETest(unittest.TestCase):

ENCRYPTED_VALUES = {"RS/CIPHERTEXT_VAL": "hellow0rld"}

def test_ssfs_data_record_decrypt(self):
"""Test decrypting a record with a given key in a SSFS Data file."""

with open(data_filename("ssfs_npl_key"), "rb") as fd:
s = fd.read()
key = SAPSSFSKeyE(s)

with open(data_filename("ssfs_npl_dat"), "rb") as fd:
s = fd.read()
data = SAPSSFSData(s)

for name, value in self.ENCRYPTED_VALUES.items():
self.assertTrue(data.has_record(name))
self.assertIsNotNone(data.get_record(name))
self.assertEqual(data.get_value(name, key), value)

record = data.get_record(name)
self.assertFalse(record.is_stored_as_plaintext)
self.assertTrue(record.valid)


def test_suite():
loader = unittest.TestLoader()
Expand Down

0 comments on commit d930e40

Please sign in to comment.