Closed rbhorse closed 1 year ago
I ended up writing a decryptor in python. But still kind of interested.
I ended up writing a decryptor in python. But still kind of interested.
wait, what? can you post that somewhere?
@nikitalita It's for 3.5.1. Apparently the engine used ECB but later switched to CFB
import sys
#pip install pycryptodome
from Crypto.Cipher import AES
save_str = b'password string here'
def encrypt(key, filepath):
print('---enc---')
with open(filepath, 'rb+') as f:
pt_dat = f.read()
enc = b''
#write magic
magic = 'GDEC'.encode()[:4]
enc += magic
print(f"magic: {magic.hex()}")
#write mode
mode = int(1).to_bytes(4, 'little')
enc += mode
print(f"mode: {mode.hex()}")
#md5 digest of plaintext
md5d = hashlib.md5(pt_dat).digest()
assert len(md5d) == 16
enc += md5d
print(f"md5d: {md5d.hex()}")
#pt data size
ptsz = int(len(pt_dat)).to_bytes(8, 'little')
enc += ptsz
print(f"ptsz: {ptsz.hex()} ({int.from_bytes(ptsz, 'little')})")
#ciphertext
cipher = AES.new(key=key,
mode=AES.MODE_ECB
)
ds = len(pt_dat)
if ds % 16 != 0:
pt_dat += b'\0'* (16 - (ds % 16))
ct_dat = cipher.encrypt(pt_dat)
enc += ct_dat
print(f"sz ct_dat: {len(ct_dat)}")
return enc
def decrypt(key, filepath):
print('---dec---')
with open(filepath, 'rb+') as f:
magic = f.read(4)
assert magic.decode() == 'GDEC'
print(f"magic: {magic.hex()}")
mode = f.read(4)
print(f"mode: {mode.hex()}")
#md5 digest of plaintext
md5d = f.read(16)
print(f"md5d: {md5d.hex()}")
lb = f.read(8)
length = int.from_bytes(lb, 'little')
print(f"pt sz (exp): {lb.hex()} ({length})")
ds = length
if ds % 16 != 0:
ds += 16 - (ds % 16)
print(f"ds: {ds}")
ct_dat = f.read(ds)
print(f"ct sz: {len(ct_dat)}")
cipher = AES.new(key=key,
mode=AES.MODE_ECB
)
pt_dat = cipher.decrypt(ct_dat)[:length]
print(f"pt sz (act): {len(pt_dat)}")
#make sure pt hash matches expected from header
assert md5d == hashlib.md5(pt_dat).digest()
return pt_dat
def main():
if len(sys.argv) != 3:
print('invalid options')
return
key = hashlib.md5(save_str).hexdigest().lower().encode()
print(f'key: {key}')
fp = sys.argv[1]
op = sys.argv[2]
if(op == 'd'):
with open(fp+".dec", 'wb+') as f:
f.write(decrypt(key, fp))
elif (op == 'e'):
with open(fp+".enc", 'wb+') as f:
f.write(encrypt(key, fp))
return
if __name__ == '__main__':
main()
Thank you for this. It seems that it's using the same encryption scheme as Godot 3.x scripts, which we have support for.
I am probably not going to add a feature to decrypt non-script files like savegames, however, as this can mess with Steam achievements. That is, you'd be messing with an external system, not just reverse engineering what's on yours. I'd rather not encourage that.
Hello; I have recovered the cfb key and it appears to be working well with the pck. However, my aim is to decrypt the save/achievements file that the game uses (custom file extension: .dv and .dvb). It seems to use the same godot encryption as it has the GDEC header. All methods I've tried so far have been futile and have lead to processing errors from gdsdecomp. What else can I try?