1

I am encrypting a string using following class (using inheritance)

class UnsafeCrypto {

const METHOD = 'aes-256-ctr';

/**
 * Encrypts (but does not authenticate) a message
 * 
 * @param string $message - plaintext message
 * @param string $key - encryption key (raw binary expected)
 * @param boolean $encode - set to TRUE to return a base64-encoded 
 * @return string (raw binary)
 */
public static function encrypt($message, $key, $encode = false)
{
    $nonceSize = openssl_cipher_iv_length(self::METHOD);
    $nonce = openssl_random_pseudo_bytes($nonceSize);

    $ciphertext = openssl_encrypt(
        $message,
        self::METHOD,
        $key,
        OPENSSL_RAW_DATA,
        $nonce
    );

    // Now let's pack the IV and the ciphertext together
    // Naively, we can just concatenate
    if ($encode) {
        return base64_encode($nonce.$ciphertext);
    }
    return $nonce.$ciphertext;
}

/**
 * Decrypts (but does not verify) a message
 * 
 * @param string $message - ciphertext message
 * @param string $key - encryption key (raw binary expected)
 * @param boolean $encoded - are we expecting an encoded string?
 * @return string
 */
public static function decrypt($message, $key, $encoded = false)
{
    if ($encoded) {
        $message = base64_decode($message, true);
        if ($message === false) {
            throw new Exception('Encryption failure');
        }
    }

    $nonceSize = openssl_cipher_iv_length(self::METHOD);
    $nonce = mb_substr($message, 0, $nonceSize, '8bit');
    $ciphertext = mb_substr($message, $nonceSize, null, '8bit');

    $plaintext = openssl_decrypt(
        $ciphertext,
        self::METHOD,
        $key,
        OPENSSL_RAW_DATA,
        $nonce
    );

    return $plaintext;
 }
}

Inheriting the class as below.

class SaferCrypto extends UnsafeCrypto {
const HASH_ALGO = 'sha256';

/**
 * Encrypts then MACs a message
 * 
 * @param string $message - plaintext message
 * @param string $key - encryption key (raw binary expected)
 * @param boolean $encode - set to TRUE to return a base64-encoded string
 * @return string (raw binary)
 */
public static function encrypt($message, $key, $encode = false)
{
    list($encKey, $authKey) = self::splitKeys($key);

    // Pass to UnsafeCrypto::encrypt
    $ciphertext = parent::encrypt($message, $encKey);

    // Calculate a MAC of the IV and ciphertext
    $mac = hash_hmac(self::HASH_ALGO, $ciphertext, $authKey, true);

    if ($encode) {
        return base64_encode($mac.$ciphertext);
    }
    // Prepend MAC to the ciphertext and return to caller
    return $mac.$ciphertext;
}

/**
 * Decrypts a message (after verifying integrity)
 * 
 * @param string $message - ciphertext message
 * @param string $key - encryption key (raw binary expected)
 * @param boolean $encoded - are we expecting an encoded string?
 * @return string (raw binary)
 */
public static function decrypt($message, $key, $encoded = false)
{
    list($encKey, $authKey) = self::splitKeys($key);
    if ($encoded) {
        $message = base64_decode($message, true);
        if ($message === false) {
            throw new Exception('Encryption failure');
        }
    }

    // Hash Size -- in case HASH_ALGO is changed
    $hs = mb_strlen(hash(self::HASH_ALGO, '', true), '8bit');
    $mac = mb_substr($message, 0, $hs, '8bit');

    $ciphertext = mb_substr($message, $hs, null, '8bit');

    $calculated = hash_hmac(
        self::HASH_ALGO,
        $ciphertext,
        $authKey,
        true
    );

    if (!self::hashEquals($mac, $calculated)) {
        throw new Exception('Encryption failure');
    }

    // Pass to UnsafeCrypto::decrypt
    $plaintext = parent::decrypt($ciphertext, $encKey);

    return $plaintext;
}

/**
 * Splits a key into two separate keys; one for encryption
 * and the other for authenticaiton
 * 
 * @param string $masterKey (raw binary)
 * @return array (two raw binary strings)
 */
protected static function splitKeys($masterKey)
{
    // You really want to implement HKDF here instead!
    return [
        hash_hmac(self::HASH_ALGO, 'ENCRYPTION', $masterKey, true),
        hash_hmac(self::HASH_ALGO, 'AUTHENTICATION', $masterKey, true)
    ];
}

/**
 * Compare two strings without leaking timing information
 * 
 * @param string $a
 * @param string $b
 * @ref https://paragonie.com/b/WS1DLx6BnpsdaVQW
 * @return boolean
 */
protected static function hashEquals($a, $b)
{
    if (function_exists('hash_equals')) {
        return hash_equals($a, $b);
    }
    $nonce = openssl_random_pseudo_bytes(32);
    return hash_hmac(self::HASH_ALGO, $a, $nonce) === hash_hmac(self::HASH_ALGO, $b, $nonce);
 }
}

Encrypting string as follow.

$email = 'myemail@domain.com';
$key = 'Some key';
$encrypted_email = $encrypted = SaferCrypto::encrypt($message, $key);

String is getting encrypted like this:

 óÜCu!>Ò·<x0½“—J ‡l¿Ø ;ƒ;›OøVP#ï 1 Sjñ,(ñúrÛòv–~ºyÍg ÷–´´iƒû

If I pass this with urlencode($string) data is not getting decrypted because hash is not matching because hashEquals method is throwing exception.

What is the proper way to pass encrypted string in GET request?

Ankit
  • 235
  • 1
  • 3
  • 17
  • 1
    `hash is not matching` do you have a hash or encryption? (Hashes can't be decrypted) – chris85 Feb 12 '17 at 16:28
  • @chris85 If you look at `decrypt` method [here](http://stackoverflow.com/a/30189841) method `hashEquals` is throwing the error. – Ankit Feb 12 '17 at 16:36
  • 1
    Your question should have all the information. – chris85 Feb 12 '17 at 16:38
  • Edited the description with complete information. – Ankit Feb 12 '17 at 16:48
  • The characters your encryption routine is producing are anything but valid within a query string. Consider applying a base64 transport coding. Also: What about HTTPS? I think that were safer and less troublesome compared to what you are trying to do here. – DaSourcerer Feb 12 '17 at 17:30
  • @DaSourcerer You mean using base64_encode function should do the trick? – Ankit Feb 12 '17 at 17:38
  • It could. Everything in abse64 is a valid char in a query string (cf [RFC 3986, section 3.4](https://tools.ietf.org/html/rfc3986#section-3.4)) – DaSourcerer Feb 12 '17 at 17:41

1 Answers1

0

It's not clear, what is the problem, but you can use base64_encode($encrypted_email) to pass your string as smth like HDo1FaXabDHjfxBkpx7YBHNGVeZ/G8FvI2BoxES2rUu3lvNWRo5G0PImiv9IhENv.

<a href="?enc=<?=base64_encode($encrypted_email)?>">link</a>

When receiving, decode it

$encrypted_email = base64_decode($_GET['enc']);
shukshin.ivan
  • 11,075
  • 4
  • 53
  • 69