user1884209
user1884209

Reputation: 29

Decrypting aes-128-gcm encrypted file with openssl_decrypt

We are trying to decrypt a file we receive with the AS4 protocol and SOAP messages. We are able to decrypt the key using 'openssl_private_decrypt'. But the decrypted key has a size of 256 bytes, and does not match the required size for the 'openssl_decrypt' key size:

$privateKey = '*hidden*';
$passPhrase = '*hidden*';
$supplierCypherValue = 'ePsYNDRENhm/PJ1+3UT7V5U94/mP/wiPtILqBea869SnZh9olcVglJcOJZc7qexii4ewb00qopIW0HP8xYn4uQ7MNoh2fDfCQUd0hwGrIxcYQL3IBijccLJHKx0FmPIOLui3z4+sXLUz5sCBzQdzJWt2q3Uz/fkfXceVbqo5tn5jMwuayQ+uzQg86Uf+fhoGI891XFeVGQgemhPc9NigIlx/Z/W9+4B1QxM+Kd5mgOQHmNl6vF/n+q6g4TIvjZSQFuYPH7/Edb0e7eqDsEHrnfRtmTanw4YEDDALu3KECTI692i2W0f0TJRtXptwjMN9k5rA2i6rQWLyFny+6x74rQ==';
$base64DecodedCipherValue = base64_decode($supplierCypherValue);
$privateKey = openssl_pkey_get_private($privateKey, $passPhrase);
$decryptedKey = '';
openssl_private_decrypt($base64DecodedCipherValue,$decryptedKey, $privateKey, OPENSSL_NO_PADDING); // Output = true
// For further testing purposes, $decryptedKey bin output in hex format:
$decryptedKeyHex = '00853d1a77f1e400c602c26bd97aaaaa408c0dbb95d92ce85713d9418cfb51fab5fc956629e769d703330530ae7ce797919abc396846a5faa81e43769bf2dc3e9c19cb548aad34967d934c8d8ddffc1ac97002ca06c9caf32d5c2f5cab4e31c18276c3860c7f7f542fbd331586b227aaae63f4d0dd94bcd0089ffabf230381bc9d107fa8bb93679415b1a2d1c53d1100d1adf966271c6753ae91ccc29a84baa5b63c1e7c4bed07a23a18c366608a55806e9073485aef9c4a500f71ff8d9888dc92bd7b7f02108fda47e9af81bcc7074b46d87efa45c474be187f8ada412359a1630daf11ff9c3d85622715ec19537ed00289f124c334af49f7ef5e8f1f833be5';
$decryptedKey = hex2bin($decryptedKeyHex);
// Continue
$cipher = 'aes-128-gcm';
$binaryData = 'binary data';
$ivlen = openssl_cipher_iv_length($cipher);
$iv = substr($binaryData, 0, $ivlen);
$ciphertext_raw = substr($binaryData, $ivlen, -16);
$tag = substr($binaryData, -16);

$decryptedData = openssl_decrypt(
    $ciphertext_raw,
    $cipher,
    $decryptedKey,
    OPENSSL_RAW_DATA,
    $iv,
    $tag
);

if (empty($decryptedData)) {
    var_dump(openssl_error_string());
    throw new \Exception('Decription failed');
}

This outputs:

string(89) "error:0607A082:digital envelope routines:EVP_CIPHER_CTX_set_key_length:invalid key length"

Exception : Decription failed

We tried several variations on the openssl_decrypt method. Changing IV or data still has the error that there is an invalid key length. The binary content should decrypt into a gzipped xml document.

Upvotes: 0

Views: 140

Answers (1)

Topaco
Topaco

Reputation: 49460

As already mentioned in the comments, the padding is not removed during RSA decryption of the AES key due to the OPENSSL_NO_PADDING option. Instead of OPENSSL_NO_PADDING, you must specify the padding used. However, from the decrypted data, it cannot be definitively determined which padding was applied. Maybe this information is available in the metadata (I am not familiar with the AS4 protocol).

Assuming a successful decryption, the data is most likely compliant with OAEP (RSAES-OAEP) due to the leading 0x00 byte and the lack of compliance with PKCS#1 v1.5 (RSAES-PKCS1-v1_5). However, the OAEP parameters, in particular the OAEP and MGF1 digest, cannot be derived from the data. Here you can only try it out, the usual digests are SHA256 or SHA1.

As PHP/OpenSSL only supports SHA1, this library is rather unsuitable for this. A PHP library that fully supports OAEP is phpseclib, for instance. Alternatively, since the decryption has already taken place, the OAEP unpadding can also be performed manually, as described in RFC 8017, Chapter 7.1.2, step 3, e.g. if SHA256 is assumed as the digest:

<?php
const DIGEST = 'sha256';
const DIGEST_LEN = 32;
const KEY_LEN = 256;
$key = hex2bin("00853d1a77f1e400c602c26bd97aaaaa408c0dbb95d92ce85713d9418cfb51fab5fc956629e769d703330530ae7ce797919abc396846a5faa81e43769bf2dc3e9c19cb548aad34967d934c8d8ddffc1ac97002ca06c9caf32d5c2f5cab4e31c18276c3860c7f7f542fbd331586b227aaae63f4d0dd94bcd0089ffabf230381bc9d107fa8bb93679415b1a2d1c53d1100d1adf966271c6753ae91ccc29a84baa5b63c1e7c4bed07a23a18c366608a55806e9073485aef9c4a500f71ff8d9888dc92bd7b7f02108fda47e9af81bcc7074b46d87efa45c474be187f8ada412359a1630daf11ff9c3d85622715ec19537ed00289f124c334af49f7ef5e8f1f833be5");
$maskedSeed = substr($key, 1, DIGEST_LEN);
$maskedDB = substr($key, 1 + DIGEST_LEN);
$seedMask =  mgf1($maskedDB, DIGEST_LEN);
$seed = _xor($maskedSeed, $seedMask);
$dbMask = mgf1($seed, KEY_LEN - DIGEST_LEN - 1);
$db = _xor($maskedDB, $dbMask);
print(bin2hex($db) . PHP_EOL); // e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012df6648fa1770d6a8ef0fd4e15f75141

// from RFC 8017, Appendix B, B.2.1
function mgf1($seed, $len) {
    $t = '';
    $count = ceil($len / DIGEST_LEN);
    for ($i = 0; $i < $count; $i++) {
        $c = pack('N', $i);
        $t .= hash(DIGEST, $seed . $c, true);
    }
    return substr($t, 0, $len);
}

function _xor($v1, $v2) {
    $res = '';
    for($i = 0; $i < strlen($v1); $i++){
        $res .= $v1[$i] ^ $v2[$i];
    }
    return $res;
}
?>

This provides a valid result, i.e. one that meets the OAEP specification (in RFC 8017, Chapter 7.1.2, step 3g: SHA256(<empty string>)|0x00...00|0x01|<data>) confirming the assumptions made:

0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012df6648fa1770d6a8ef0fd4e15f75141

which gives the following 16 bytes AES key:

0x2df6648fa1770d6a8ef0fd4e15f75141

Upvotes: 1

Related Questions