1

I need to implement file decryption in php. As a result of my attempts, I get a broken archive

I use the code as a basis https://github.com/samloader/samloader/tree/master

I'm interested in the part

https://github.com/samloader/samloader/blob/0e53d8032699a4039ea6f5310ebec05f8f417f07/samloader/main.py#L72

    elif args.command == "decrypt":
        getkey = crypt.getv4key if args.enc_ver == 4 else crypt.getv2key
        key = getkey(args.fw_ver, args.dev_model, args.dev_region)
        length = os.stat(args.in_file).st_size
        with open(args.in_file, "rb") as inf:
            with open(args.out_file, "wb") as outf:
                crypt.decrypt_progress(inf, outf, key, length)

and

https://github.com/samloader/samloader/blob/0e53d8032699a4039ea6f5310ebec05f8f417f07/samloader/crypt.py#L35

def decrypt_progress(inf, outf, key, length):
    """ Decrypt a stream of data while showing a progress bar. """
    cipher = AES.new(key, AES.MODE_ECB)
    if length % 16 != 0:
        raise Exception("invalid input block size")
    chunks = length//4096+1
    pbar = tqdm(total=length, unit="B", unit_scale=True)
    for i in range(chunks):
        block = inf.read(4096)
        if not block:
            break
        decblock = cipher.decrypt(block)
        if i == chunks - 1:
            outf.write(unpad(decblock))
        else:
            outf.write(decblock)
        pbar.update(4096)

Part https://github.com/samloader/samloader/blob/0e53d8032699a4039ea6f5310ebec05f8f417f07/samloader/crypt.py#L18

def getv4key(version, model, region):
    """ Retrieve the AES key for V4 encryption. """
    client = fusclient.FUSClient()
    version = versionfetch.normalizevercode(version)
    req = request.binaryinform(version, model, region, client.nonce)
    resp = client.makereq("NF_DownloadBinaryInform.do", req)
    root = ET.fromstring(resp)
    fwver = root.find("./FUSBody/Results/LATEST_FW_VERSION/Data").text
    logicval = root.find("./FUSBody/Put/LOGIC_VALUE_FACTORY/Data").text
    deckey = request.getlogiccheck(fwver, logicval)
    return hashlib.md5(deckey.encode()).digest()

I rewrote it, there are doubts about this line

hashlib.md5(deckey.encode()).digest()

I got it like this `$deckey = 'AU77D7K3SAU/D3UU';

$key = hash('md5', $deckey, true);`

This is right?

And the part of the code that is responsible for my decryption

<?php
        $source = 'file.zip.enc4';
        $dest = 'file.zip';
        
        $source = fopen($source, 'r');
        $dest = fopen($dest, 'w');
        $chunkSize = 4096;
        while (!feof($source)) {
            $chunk = fread($source, $chunkSize + 1);
            if (!$chunk) {
                break;
            }
            $encryptedChunk = openssl_decrypt($chunk, 'aes-128-ecb', $key, OPENSSL_RAW_DATA);
            fwrite($dest, $encryptedChunk);
        }
        fclose($source);
        fclose($dest);

Help me figure out what I'm doing wrong

File for testing


Edit: ...in my code I used openssl_decrypt, there was an error when I wrote here. The problem seems to be with php

$source = 'file.zip.enc4'; 
$dest = 'file.zip'; 

$source = fopen($source, 'r'); 
$dest = fopen($dest, 'w'); 
$chunkSize = 4096;
while (!feof($source)) {
    $chunk = fread($source, $chunkSize + 1);
    $encryptedChunk = openssl_decrypt($chunk, 'aes-128-ecb', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
    var_dump(openssl_error_string());
    fwrite($dest, $encryptedChunk);}
fclose($source);
fclose($dest);

i have errors

string(58) "error:1C80006B:Provider routines::wrong final block length" 

openssl works through the console. I'll do it via exec('openssl...', $res);

9
  • 2
    4 GB - why does the test file have to be so large? Can't you post a smaller test file? And which variable in the Python code should have the value AU77D7K3SAU/D3UU, deckey in getv4key()? Commented Dec 23, 2023 at 12:51
  • Unfortunately, I don't have any other files. The deckey is correct, I checked. Doubts with the hash $key = hash('md5', $deckey, true); Commented Dec 23, 2023 at 14:04
  • ...Help me figure out what I'm doing wrong... You do not describe what is not working, or why you think it is not working, e.g. is an exception thrown (if so, which one?), does the output not match the expected output (e.g. not a valid zip file) etc.? Commented Dec 23, 2023 at 14:27
  • @Topaco There are no mistakes. The code works, but the received zip archive is not opened by the archiver (broken archive). Commented Dec 23, 2023 at 14:35
  • At first glance, you don't seem to take into account in the PHP code that PHP/OpenSSL pads by default (or unpads when decrypting). Commented Dec 23, 2023 at 14:38

1 Answer 1

0

The porting of the key derivation via MD5 that you suggested is correct.

However, there is an issue with the padding: From the Python code, it can be concluded that the PKCS#7 padding is disabled for all chunks except the last one during encryption. This must be taken into account when decrypting with the PHP code.

A possible PHP implementation (based on your code) is:

$source = 'file.zip.enc4';
$dest = 'file.zip';

$deckey = "AU77D7K3SAU/D3UU";
$key = hash('md5', $deckey, true);

$chunkSize = 4096;
$fileSize = filesize($source);

$source = fopen($source, 'rb');
$dest = fopen($dest, 'wb');

$totalBytesRead = 0;
while (!feof($source)) {
    $chunk = fread($source, $chunkSize);
    if (!$chunk) {
        break;
    }
    $totalBytesRead += strlen($chunk);
    $padding = ($totalBytesRead < $fileSize) ? OPENSSL_ZERO_PADDING : 0;
    $decryptedChunk = openssl_decrypt($chunk, 'aes-128-ecb', $key, OPENSSL_RAW_DATA | $padding);
    fwrite($dest, $decryptedChunk);
}

fclose($source);
fclose($dest);

Explanation: OPENSSL_ZERO_PADDING disables the default PKCS#7 padding in PHP/OpenSSL (note that the name of the flag is misleading and could assume the enabling of Zero padding; but in fact this flag disables the default padding).
The code above determines the total number of bytes read so far. If this is less than the file size, the chunk just read is not the last one, so the default padding must be disabled, otherwise it is the last chunk and the default padding remains enabled.


The decrypted file file.zip (4337718623 bytes) is one byte smaller than the encrypted file file.zip.enc4 (4337718624 bytes), which is due to the padding of the last chunk.
The decrypted file can be successfully extracted and provides an archive with 5 .tar.md5-files.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.