pythonphpencryptionopensslphp-openssl

Rewrite a function from python to php


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);


Solution

  • 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.