0

I'm at the moment realizing an S/Mime decryption using PHP. What i got so far:

    $keys = array("public"=>$atm."/public-keys/".$usr.".smime",
        "private"=>$atm."/private-keys/".$usr.".smime");
    if(!file_exists($keys["public"])) die("Public Key not found");
    if(!file_exists($keys["private"])) die("Private Key not found");
    $public = file_get_contents($keys["public"]);
    $private = file_get_contents($keys["private"]);

    switch($_GET["debug"])
    {
        case "encrypt":
        {
            $outfile = realpath("demo-msg/out.txt");
            $outfile_signed = realpath("demo-msg/out.signed.txt");
            $infile = realpath("demo-msg/in.txt");

            file_put_contents($infile,$msg);
            $adddata = array("To" => "XXX", "From: Demo Name <XXX>", "Subject" => "Demo Subject");
            if (openssl_pkcs7_encrypt($infile, $outfile, $public, $adddata))
            {
                //$info = file_get_contents($outfile);
                echo "winenc & transfer<br>\n";
                file_put_contents($infile, file_get_contents($outfile));
                //if(openssl_pkcs7_sign($outfile,$outfile_signed,$public,$private,$adddata, PKCS7_BINARY)) echo "winsign";
                //else echo "failsign";
            } 
            else echo "Failed Encryption";
            exit;
        }
        default:
        {
            $outfile2 = realpath("demo-msg/out2.txt");
            $outfile = realpath("demo-msg/out.txt");
            $infile = realpath("demo-msg/smime.p7m");
            //$infile = realpath("demo-msg/in.txt");

            if(openssl_pkcs7_verify($infile)) echo "verified<br>\n"; //tried: openssl_pkcs7_verify($infile,$PKCS7_DETACHED, tmpfile(), array(), array(), $outfile)
            else die("invalid sig");

            if(openssl_pkcs7_decrypt($infile, $outfile2, $public, $private)) //tried: openssl_pkcs7_decrypt($outfile, $outfile2, $public, $private)
            {
                echo "dec win:".file_get_contents($outfile2);
            }
            else echo "Oh oh! Decryption failed!";
            exit;
        }
    }

What this snippet already can do:

  • Encrypt a Message
  • Decrypt an encrypted message (created by itself)
  • Decrypt an encrypted message (Office 2010) as long as it's not signed

Now, i want to decrypt messages that are signed too (as it's usually one step). The Problem:

  • If i first try a decrypt, it'll return the encrypted message with different headers. Multiple decryptions lead to the same result.
  • My thought was using the $content - parameter of the verification - command (openssl_pkcs7_verify). You can see my attempt in the code-comments.

Nevertheless, i don't have any clues what can be wrong with the second attempt. ANy help would be appreciated!

James Cameron
  • 1,681
  • 25
  • 40

1 Answers1

1

Speaking to myself.

Errors i made in the script:

  • Verification returns -1 (error), but i process it as true (success). The verification never worked.
  • The Verification is plain wrong. The DETACHED is a constant, not a variable. The "extracert" parameter expects a valid file as string containing a valid signature. Though, the idea i had was right (de-signing using the "content"-parameter).
  • Order of signing and decryption

What i misunderstood was the way, signatures are processed (and verifications are made). I assumed the message gets encrypted, then signed. It can be that way, but a lot of tools, including Office2010 first sign the message, then encrypt it. That way you can't check the signature before decrypting and have to de-sign after the decryption.

You can see my debugging-code that works down below. This will help you with your decryption problems when stumbling upon this thread.

            $test = openssl_pkcs7_verify($infile, PKCS7_DETACHED ); //just to see that it doesn't work
            echo "signature is ".$test."\n<br>".openssl_error_string(); 

            $dec = openssl_pkcs7_decrypt($infile, $outfile, $public, $private);
            echo "<br><br>\n\ndec is ".$dec."\n<br>".openssl_error_string()."\n<br>".file_get_contents($outfile); 

            $test = openssl_pkcs7_verify($outfile, PKCS7_DETACHED, $tmp, array(), $tmp, $outfile2 );
            echo "<br><br>\n\nsignature2 is ".$test."\n<br>".openssl_error_string()."\n<br>".file_get_contents($outfile2); 
James Cameron
  • 1,681
  • 25
  • 40
  • 1
    Thanks for reporting back. This will solve your *programming error*. Note however that by default PHP seems to use RC2 encryption with a 40 bit key (i.e. decryption is even instantaneous if you don't know the key). Furthermore, if you switch to AES-CBC encoding you should first verify, then decrypt (i.e. the signature should be over the signed message) otherwise you will be vulnerable to (e.g.) padding oracle attacks. Be warned that "it works" does not imply "it is now secure". – Maarten Bodewes Sep 06 '14 at 14:15
  • Thanks for the response. Even though, i'm not really sure how to handle that information. First: You're talking about encryption, while i deal with decryption. Second: You don't give hints how to change the encoding. Third: I'm pretty sure i've read in multiple sources that a lot of clients first sign, then encrypt. Your way round therefore won't work. More informations would be great :) – James Cameron Sep 06 '14 at 15:03
  • 1
    There is a lot of discussion going on in the cryptographic community about sign/encrypt or encrypt/sign. If you use CBC mode for encryption in a transport protocol, you'll quickly loose confidentiality if you don't verify the signature first (and that's what you tried to achieve in the first place). If you only decrypt then for sure, you can only decrypt what's send to you. Just be aware that what you decrypt this way may not be secure just because PKCS#7 (CMS) is being applied - also check/log the actual configuration used. – Maarten Bodewes Sep 06 '14 at 15:23