User:Pedro Oval/Base64 HMAC SHA-1

From Second Life Wiki
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

TODO: Add some text here, add padding test vectors

string HexToBase64Unpadded(string a)
{
    integer i = 0;
    integer len = llStringLength(a += "0000") - 4;
    string res = "";
    while (i < len)
    {
        res += llGetSubString(llIntegerToBase64(4*(integer)("0x" + llGetSubString(a, i, i+5))), 1, 4);
        i += 6;
    }
    return llGetSubString(res, 0, len*2/3);
}

string RemovePadding(string b64) // specialized - it won't work if the string consists of all "="
{
    integer i;
    do ; while(llGetSubString(b64, i = ~-i, i) == "=");
    return llGetSubString(b64, 0, i);
}

list Base64SHA1Compress(string b64, integer H1, integer H2, integer H3, integer H4, integer H5, integer i)
{
    integer A = H1;
    integer B = H2;
    integer C = H3;
    integer D = H4;
    integer E = H5;

    integer round;
    integer S = 0;
    integer T;
    list x = [];
    string buf;

    do
    {
        T = llBase64ToInteger(buf = llGetSubString(b64, T = ((i + round) << 4) / 3, T+6)) << (S = ((i + round) % 3) << 1);
        if(S)
            T = T | (llBase64ToInteger("A" + (llDeleteSubString(buf, 0, 1))) >> (6 - S));
//            llOwnerSay("W["+(string)round+"]="+hex(T));
        x += T;
        T += ((A << 5) | ((A >> 27) & 0x1F)) + (D ^ (B & (C ^ D))) + E + 0x5a827999;
        E = D;
        D = C;
        C = ((B << 30) | ((B >> 2) & 0x3FFFFFFF));
        B = A;
        A = T;
    }while(16 > (round = -~round));
//        llOwnerSay(llList2CSV(hexm(x)));
    do
    {
        S = llList2Integer(x,  -3) ^ llList2Integer(x,  -8) ^ llList2Integer(x, -14) ^ llList2Integer(x, -16);
        x = llList2List(x + (T = ((S << 1) | !!(S & 0x80000000))), -16, -1);
//            llOwnerSay("W["+(string)round+"]="+hex(T));
        T += ((A << 5) | ((A >> 27) & 0x1F)) + (D ^ (B & (C ^ D))) + E + 0x5a827999;
        E = D;
        D = C;
        C = ((B << 30) | ((B >> 2) & 0x3FFFFFFF));
        B = A;
        A = T;
    }while(20 > (round = -~round));
    do
    {
        S = llList2Integer(x,  -3) ^ llList2Integer(x,  -8) ^ llList2Integer(x, -14) ^ llList2Integer(x, -16);
        x = llList2List(x + (T = ((S << 1) | !!(S & 0x80000000))), -16, -1);
//            llOwnerSay("W["+(string)round+"]="+hex(T));
        T += ((A << 5) | ((A >> 27) & 0x1F)) + (B ^ C ^ D) + E + 0x6ed9eba1;
        E = D;
        D = C;
        C = ((B << 30) | ((B >> 2) & 0x3FFFFFFF));
        B = A;
        A = T;
    }while(40 > (round = -~round));
    do
    {
        S = llList2Integer(x,  -3) ^ llList2Integer(x,  -8) ^ llList2Integer(x, -14) ^ llList2Integer(x, -16);
        x = llList2List(x + (T = ((S << 1) | !!(S & 0x80000000))), -16, -1);
//            llOwnerSay("W["+(string)round+"]="+hex(T));
        T += ((A << 5) | ((A >> 27) & 0x1F)) + ((B & C) | (B & D) | (C & D)) + E + 0x8f1bbcdc;
        E = D;
        D = C;
        C = ((B << 30) | ((B >> 2) & 0x3FFFFFFF));
        B = A;
        A = T;
    }while(60 > (round = -~round));
    do
    {
        S = llList2Integer(x,  -3) ^ llList2Integer(x,  -8) ^ llList2Integer(x, -14) ^ llList2Integer(x, -16);
        x = llList2List(x + (T = ((S << 1) | !!(S & 0x80000000))), -16, -1);
//            llOwnerSay("W["+(string)round+"]="+hex(T));
        T += ((A << 5) | ((A >> 27) & 0x1F)) + (B ^ C ^ D) + E + 0xca62c1d6;
        E = D;
        D = C;
        C = ((B << 30) | ((B >> 2) & 0x3FFFFFFF));
        B = A;
        A = T;
    }while(80 > (round = -~round));

    return [H1+A, H2+B, H3+C, H4+D, H5+E];
}

string Base64SHA1forHMAC(string b64, integer bit_length, integer extra_bit_length, integer H1, integer H2, integer H3, integer H4, integer H5)
{
    //OR on the extra bit.
    integer b = ((bit_length + 40) >> 5) | 15;
    integer T = llBase64ToInteger(RemovePadding(llGetSubString(b64, -4, -1)) + "AAAA");
    string buf = "AAA";
    integer i = -5;
    do buf += buf; while((i = -~i));
    if(bit_length)
    {
        i = 0x800000;
        if(!(bit_length % 24))
            i = 0x80;
        else if((bit_length % 24) == 16)
            i = 0x8000;
    }
    else
        T = 0x80000000;//T is corrupt because of https://jira.secondlife.com/browse/SVC-104
    bit_length += extra_bit_length;
//    llOwnerSay(llList2CSV([i,bit_length]));
    b64 = llGetSubString( llDeleteSubString(b64, -4, -1) + 
                          llGetSubString(llIntegerToBase64(T | i), 0, 5) + 
                          buf, 0, (b << 4) / 3) + 
          llIntegerToBase64(bit_length << (6 - ((b % 3) << 1)));
    list x;
    i = 0;
    do
    {
        x = Base64SHA1Compress(b64, H1, H2, H3, H4, H5, i);
        H1 = llList2Integer(x, 0);
        H2 = llList2Integer(x, 1);
        H3 = llList2Integer(x, 2);
        H4 = llList2Integer(x, 3);
        H5 = llList2Integer(x, 4);
    }while(b > (i += 16));
    x = [H1, H2, H3, H4, H5];
    i = -5;
    buf = "";
    do
    {
        T = llList2Integer(x,i);
        bit_length = 32;
        do
            buf += llGetSubString("0123456789abcdef", b = ((T >> (bit_length -= 4)) & 0xF), b);
        while (bit_length);
    }while ((i = -~i));
    return buf;
}


// The Real Thing
string Base64_HMAC_SHA1(string B64Key, string B64Data)
{
//    integer H1 = 0x67452301;
//    integer H2 = 0xefcdab89;
//    integer H3 = 0x98badcfe;
//    integer H4 = 0x10325476;
//    integer H5 = 0xc3d2e1f0;
    integer bit_length = llSubStringIndex(B64Key, "=");
    if (!~bit_length)
    {
        bit_length = llStringLength(B64Key);
    }
    if (bit_length > 86)
    {
        // Use a hash of the key instead as a key
        B64Key = HexToBase64Unpadded(Base64SHA1forHMAC(B64Key, (bit_length*6)&-8, 0, 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0));
        bit_length = 27;
    }
    B64Key = llGetSubString(B64Key, 0, bit_length-1);
    string buf = "AAA";
    integer i = -5;
    do buf += buf; while((i = -~i));
    B64Key = llXorBase64StringsCorrect(llGetSubString(B64Key + buf, 0, 85) + "==", "NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2Ng==");

    list x = Base64SHA1Compress(B64Key, 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0, 0);
    bit_length = (6 * !(B64Data=="") * (llStringLength(B64Data)-4+llStringLength(RemovePadding(llGetSubString(B64Data,-4,-1))))) & -8;
    B64Data = HexToBase64Unpadded(Base64SHA1forHMAC(B64Data, bit_length, 512, llList2Integer(x, 0), llList2Integer(x, 1), llList2Integer(x, 2), llList2Integer(x, 3), llList2Integer(x, 4))) + "=";

    // Xor with Base64(chr(0x36 xor 0x5C) * 64)
    B64Key = llXorBase64StringsCorrect(B64Key, "ampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqampqag==");
    x = Base64SHA1Compress(B64Key, 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0, 0);
    bit_length = 160;
    return Base64SHA1forHMAC(B64Data, bit_length, 512, llList2Integer(x, 0), llList2Integer(x, 1), llList2Integer(x, 2), llList2Integer(x, 3), llList2Integer(x, 4));
}


test_vector_SHA1(string B64Key, string B64Data, string HexHash)
{
    llResetTime();
    string hmac = Base64_HMAC_SHA1(B64Key, B64Data);
    if (hmac != HexHash)
    {
        llOwnerSay("HMAC-SHA1 test failed.\nComputed: " + hmac + "\nExpected : " + HexHash);
    }
    else
    {
        llOwnerSay("HMAC-SHA1 test passed in " + (string)llGetTime() + " seconds.");
    }
}

 
default
{
    state_entry()
    {
        // RFC 2202 test vectors:

        test_vector_SHA1(HexToBase64Unpadded("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b") + "=",
                         llStringToBase64("Hi There"),
                         "b617318655057264e28bc0b6fb378c8ef146be00");
        test_vector_SHA1(llStringToBase64("Jefe"),
                         llStringToBase64("what do ya want for nothing?"),
                         "effcdf6ae5eb2fa2d27416d5f184df9c259a7c79");
        test_vector_SHA1("qqqqqqqqqqqqqqqqqqqqqqqqqqo=",
                         "3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d0=",
                         "125d7342b9ac11cd91a39af48aa17b4f63f175d3");
        test_vector_SHA1(HexToBase64Unpadded("0102030405060708090a0b0c0d0e0f10111213141516171819")+"==",
                         HexToBase64Unpadded("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd")+"=",
                         "4c9007f4026250c6bc8414f9bf50c86c2d7235da");
        test_vector_SHA1(HexToBase64Unpadded("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c") + "=",
                         llStringToBase64("Test With Truncation"),
                         "4c1a03424b55e07fe7f27be1d58bb9324a9a5a04");
        string buf = "aaaaa";
        buf += buf;
        buf += buf;
        buf += buf;
        buf += buf;
        buf += buf;
        test_vector_SHA1(HexToBase64Unpadded(buf) + "=",
                         llStringToBase64("Test Using Larger Than Block-Size Key - Hash Key First"),
                         "aa4ae5e15272d00e95705637ce8a3b55ed402112");
        test_vector_SHA1(HexToBase64Unpadded(buf) + "=",
                        llStringToBase64("Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data"),
                        "e8e99d0f45237d786d6bbaa7965c7808bbff1a91");

        // Test vector added by the author
        test_vector_SHA1("", "", "fbdb1d1b18aa6c08324b7d64b71fb76370690e1d");
        // Test vectors added by the author for zero-terminated data strings at different padding positions
        // (generated with an independent program written in PHP):
        test_vector_SHA1("blah", "",         "cf63ad15d4e1d7cdc33cb0084e362afe60d047b1"); // ""
        test_vector_SHA1("blah", "AA==",     "36cda2fc2a348a0b3645a9c135a12118e6f4dcca"); // "\0"
        test_vector_SHA1("blah", "AAA=",     "7b6527796c885a794f695541b4ec1a5af28dec3e"); // "\0\0"
        test_vector_SHA1("blah", "AAAA",     "d79915ec627764b7092ec66036fcaf94e4d94091"); // "\0\0\0"
        test_vector_SHA1("blah", "MQA=",     "9fa63e8684813ca6f601e7e082fd989c72295019"); // "1\0"
        test_vector_SHA1("blah", "MQAA",     "0d1ad14b1c4da14d5fca5b2e3486a7df2ef5fa12"); // "1\0\0"
        test_vector_SHA1("blah", "MQAAAA==", "29ba52e7d932fdb2854cf52f75dea30aa0773025"); // "1\0\0\0"
        test_vector_SHA1("blah", "MTIA",     "ee65d68cdba172a95b7cc1640573bfa4f10fcf9d"); // "12\0"
        test_vector_SHA1("blah", "MTIAAA==", "4908fdece951bc779690565b54960a5e93119d56"); // "12\0\0"
        test_vector_SHA1("blah", "MTIAAAA=", "3213b2e563af1d5c04159bfcee0ee81124aaefb7"); // "12\0\0\0"
        test_vector_SHA1("blah", "MTIzAA==", "f3967f44b64b8a32b3d0c9cfe514e580aca6869d"); // "123\0"
        test_vector_SHA1("blah", "MTIzAAA=", "e509dfb925f1bd533216ba1f44d3ba1a998d078c"); // "123\0\0"
        test_vector_SHA1("blah", "MTIzAAAA", "06476a68a5c2c6cfc5c595b83dcc672e910a3e3e"); // "123\0\0\0"

        llOwnerSay("End of tests.");
    }
}