3

DESCRIPTION:

It seems that I can't figure out how does PHP produce following:

echo hash_hmac("sha1", "what is this", true);
echo PHP_EOL; // end of line
echo base64_encode(hash_hmac("sha1", "what is this", true));

Link to see output online (copy/paste required).

As documentation says, you need data and key with proper output to create SHA1 HMAC hash.

string hash_hmac(string $algo, string $data, string $key [, bool $raw_output = false])

What I want is to create exact output with csharp and javascript.


TRIED:

It's fine when I have data and key and I can produce same SHA1 HMAC hash on csharp and javascript.

// PHP
echo base64_encode(hash_hmac("sha1", "data", "key", true));

Link to see output online (copy/paste required).

// CSharp
public static void Main(string[] args)
{
    Console.WriteLine(CreatePhpSha1HmacHash("data", "key"));
}

public static string CreatePhpSha1HmacHash(string data, string key)
{
    if (data == null)
    {
        data = string.Empty;
    }

    var encoding = new System.Text.UTF8Encoding(); // It's UTF-8 for my example

    var keyBytes = encoding.GetBytes(key);
    var dataBytes = encoding.GetBytes(data);

    using (var hmac = new System.Security.Cryptography.HMACSHA1(keyBytes))
    {
        var hash = hmac.ComputeHash(dataBytes);

        return Convert.ToBase64String(hash);
    }
}

Link to see output online (copy/paste required).

// Javascript
var hash = CryptoJS.HmacSHA1('data', 'key');
var base64 = CryptoJS.enc.Base64.stringify(hash);

console.log('Sha1 hmac hash: ' + base64);

Link to see output online.


QUESTION:

How can I create exact output as php example shown in description when it's not using required two parameters? Can someone explain to me what does php is doing in that case?


ANSWER:

@GentlemanMax: PHP will internally cast TRUE to a STRING so KEY will be converted to "1" as string value. When raw_output is set to TRUE, it outputs raw binary data. FALSE outputs lowercase hexits.


SOLUTION:

// CSharp
public static void Main(string[] args)
{
    // echo base64_encode(hash_hmac("sha1", "what is this", true));
    // echo base64_encode(hash_hmac("sha1", "what is this", true, false));
    Console.WriteLine(ToBase64EncodedHmacSha1("what is this", "1", false));
    Console.WriteLine(ToBase64EncodedHmacSha1("what is this", "1", false.ToString()));

    // echo base64_encode(hash_hmac("sha1", "what is this", true, true));
    Console.WriteLine(ToBase64EncodedHmacSha1("what is this", "1", true));
    Console.WriteLine(ToBase64EncodedHmacSha1("what is this", "1", true.ToString()));
}

public static string ToBase64EncodedHmacSha1(string data, string key, bool rawOutput = false)
{
    bool result;

    if (bool.TryParse(key, out result))
    {
        key = result ? 1.ToString() : 0.ToString();
    }

    var keyBytes = Encoding.UTF8.GetBytes(key);
    var dataBytes = Encoding.UTF8.GetBytes(data);

    using (var hmac = new HMACSHA1(keyBytes))
    {
        var hash = hmac.ComputeHash(dataBytes);

        if (rawOutput)
        {
            // output: raw binary
            return Convert.ToBase64String(hash);
        }

        // Convert an array of bytes to a string of hex digits.
        var hex = string.Concat(hash.Select(x => x.ToString("x2").ToLower()));

        var hexBytes = Encoding.UTF8.GetBytes(hex);

        // output: lowercase hexits
        return Convert.ToBase64String(hexBytes);
    }
}

Link to see output online (copy/paste required).


// Javascript 
function toBase64EncodedHmacSha1(data, key, rawOutput) {
    // if boolean, cast to string
    if (typeof(key) === 'boolean') {
        key = key ? '1' : '0';
    }

    // optional
    if (typeof(rawOutput) === 'undefined') {
        rawOutput = false;
    }

    // check type
    if (typeof(rawOutput) !== 'boolean') {
        throw new Error('Raw output is Boolean value: true/false');
    }

    var hash = CryptoJS.HmacSHA1(data, key);

    if (rawOutput) {
        // output: raw binary
        return CryptoJS.enc.Base64.stringify(hash);
    }

    var hex = CryptoJS.enc.Hex.stringify(hash);
    var wordArray = CryptoJS.enc.Utf8.parse(hex);

    // output: lowercase hexits
    return CryptoJS.enc.Base64.stringify(wordArray);
}

// echo base64_encode(hash_hmac("sha1", "what is this", true));
// echo base64_encode(hash_hmac("sha1", "what is this", true, false));
console.log(toBase64EncodedHmacSha1('what is this', true));
console.log(toBase64EncodedHmacSha1('what is this', true, false));

// echo base64_encode(hash_hmac("sha1", "what is this", true, true));
console.log(toBase64EncodedHmacSha1('what is this', true, true));

console.log(toBase64EncodedHmacSha1('what is this', true, 'This will throw error'));

Link to see output online.

1 Answer 1

1

The key takeaway here is that PHP will internally cast true to a string. In php, true casts to "1" so

hash_hmac("sha1", "data", true);

Is exactly equivalent to

hash_hmac("sha1", "data", "1")

If you don't pass a 4th parameter to hash_hmac then it outputs the hash in hex. Which is not what you are doing in your c# or javascript. Here are some equivalencies for you to work with:

//PHP
hash_hmac("sha1", "data", true)

will output the same as

//JS
var hash = CryptoJS.HmacSHA1('data', "1")
console.log ( CryptoJS.enc.Hex.stringify(hash) ); //Note .Hex instead of .Base64

Similarly,

//PHP
base64_encode(hash_hmac("sha1", "data", true, true));

is the same as doing

//JS
var hash = CryptoJS.HmacSHA1('data', "1")
console.log ( CryptoJS.enc.Base64.stringify(hash) );

As an aside, PHP will try to cast all non-string values for $key to be a string. You can always check what something is cast to by calling strval($key) to see what you are actually using for the key.

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.