Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsWhite Papers
Discussion GroupsFirst AidDatabasesJavaBeansGUIJava 3DVirtual MachineCORBASecurityToolsGeneral
Java DirectoryOpen Source ProjectsSample Book ChaptersUser GroupsWeb Resources
Related Topics
Databases.NETMore Topics ...

Java Forum / Security / June 2004

Tip: Looking for answers? Try searching our database.

Sun's Java JCE 1.22 producing different MD5 MAC to Microsoft's CryptoAPI

Thread view: 
Adrian - 01 Jun 2004 07:48 GMT
Hi Guys,

I am writing a Java application which needs to produce an RSA MD5 Mac
code for a message, using Sun's JCE version 1.22.
This Mac code then gets validated bt a 3rd party application written
in C++, which is using Microsoft's CryptoAPI running on Windows 2000
Professional.
The problem is that the MS & Java functions seems to produce 2
different MAC values using the same key and the same input data.

My code is as follows:

This is the JAVA Code:

    public String test(String message,  byte[] keyBytes) throws Exception
    {
        Provider sunJce = new com.sun.crypto.provider.SunJCE();
        Security.insertProviderAt(sunJce,0);

        //create key object
        SecretKey key = new SecretKeySpec(keyBytes, "HmacMD5");
       
        // Create a MAC object using HMAC-MD5 and initialize with key
        Mac mac = Mac.getInstance("HmacMD5");
        mac.init(key);
               
        // Encode the string into bytes using utf-8 and digest it
        byte[] utf8 = message.getBytes();
        byte[] digest = mac.doFinal(utf8);

        // If desired, convert the digest into a string
        String digestB64 = new sun.misc.BASE64Encoder().encode(digest);           
   
        return digestB64;
    }
   
*****************************************************************************

This is the C++ Code:

HRESULT CSecureDataMgr::HashData(CByteBuffer&
bufBytesData1,CByteBuffer& bufBytesData2,CByteBuffer& bufHashBuffer,
CComBSTR& bstrErrMsg)
{
    HRESULT hr = S_OK;
    BOOL bHashed = FALSE;

   CRYPT_HASH_MESSAGE_PARA stHashParms;  
    BYTE * paHashBuffer = NULL;
    DWORD dwHashBufferSize = DEFAULT_HASH_BUFFER_SIZE;
    const BYTE* rgpbToBeHashed[3];    
    DWORD rgcbToBeHashed[3];

    DebugTrace(_T("CSecureDataMgr::HashData() has been called. Data
buffer size is (%d) bytes. Context buffer size is (%d)
bytes."),bufBytesData1.m_dwSize,bufBytesData2.m_dwSize);

    // Initialize hashing parameters.
    ZeroMemory(&stHashParms,sizeof(stHashParms));
    ZeroMemory(&rgpbToBeHashed,sizeof(rgpbToBeHashed));
    ZeroMemory(&rgcbToBeHashed,sizeof(rgcbToBeHashed));

    // We want hash the contents of the BSTR
    stHashParms.cbSize = sizeof(stHashParms);
   
    // Set the encoding type.
    stHashParms.dwMsgEncodingType = X509_ASN_ENCODING |
PKCS_7_ASN_ENCODING;

    // Set the cryptographic provider name to null (this will default to
RSA)
    stHashParms.hCryptProv = NULL;

    // set auxillary info to null (we dont use it)
    stHashParms.pvHashAuxInfo = NULL;

    // Set the algorithm used to perform the hashing. Note we opt for RSA
MD5 here.
    stHashParms.HashAlgorithm.pszObjId = szOID_RSA_MD5;
    stHashParms.HashAlgorithm.Parameters.cbData = 0;

    while (SUCCEEDED(hr) && (bufHashBuffer.m_pBytes == NULL))
    {
        // initialize hashing buffer structure
        rgpbToBeHashed[0] = g_abCSecureDataMgrKey;
        rgcbToBeHashed[0] = sizeof(g_abCSecureDataMgrKey);
        rgpbToBeHashed[1] = bufBytesData1.m_pBytes;
        rgcbToBeHashed[1] = bufBytesData1.m_dwSize;
        rgpbToBeHashed[2] = bufBytesData2.m_pBytes;
        rgcbToBeHashed[2] = bufBytesData2.m_dwSize;

        //  Allocate and intialize a buffer to receive the hashed value.
        bufHashBuffer.Alloc(dwHashBufferSize);

        // Attempt to hash the message.
        bHashed = CryptHashMessage(&stHashParms,
                                  TRUE,
                                  3,
                                  rgpbToBeHashed,
                                  rgcbToBeHashed,
                                  NULL,
                                  NULL,
                                  bufHashBuffer.m_pBytes,
                                  &dwHashBufferSize);

******************************************************************************

After I convert the C++ result to a string I get a completely
different MAC value to the Java value.

What am I doing wrong? Are the Sun and Microsoft algorithms the same?

Thanks,

Adrian
Michael Amling - 01 Jun 2004 11:51 GMT
> Hi Guys,
>
> I am writing a Java application which needs to produce an RSA MD5 Mac

  What is an RSA MD5 MAC? Where is it defined? The MD5 algorithm is
from RSA, but the HMAC construct is not.

> code for a message, using Sun's JCE version 1.22.
> This Mac code then gets validated bt a 3rd party application written
[quoted text clipped - 21 lines]
>         // Encode the string into bytes using utf-8 and digest it
>         byte[] utf8 = message.getBytes();

  This getBytes is using the default encoding, not necessarily UTF-8.

>         byte[] digest = mac.doFinal(utf8);
>
[quoted text clipped - 3 lines]
>         return digestB64;
>     }

  I can't really read the C++ code, but I couldn't find the characters
"HMAC" in it. What in the code determines that it is generating an HMAC?

> After I convert the C++ result to a string I get a completely
> different MAC value to the Java value.

  Dump out the keys, and the bytes of the messages, and the HMAC
outputs, in hex, in both implementations.

> What am I doing wrong? Are the Sun and Microsoft algorithms the same?

  The HMAC-MD5 of the message "abc" (hex 616263), using an HMAC key of
16 bytes of binary zeros, is 0C7F795D3F611FAE11252AE8DDA603D7. Does
either of your implementations produce this?

--Mike Amling
Adrian - 15 Jun 2004 06:16 GMT
> > Hi Guys,
> >
[quoted text clipped - 55 lines]
>
> --Mike Amling

PROBLEM SOLVED!!

After 2 weeks of playing with this I managed to get the correct MD5 in
JCE hash.

These are the steps required:

1) I discovered that callintg CryptHashMessage in the MS Crypto API
with an array of strings to hash, is the same as calling the
CryptCreateHash method multiple times, OR just calling it once with a
concatenated string that contains the concatenated contents of all of
the array elements used in the CryptHashMessage call.

2) Calling CryptHashMessage using szOID_RSA_MD5 as the hash algorithm
corresponds to calling CryptCreateHash using CALG_MD5 (or 32771) as
the algorithm.

3) The difference between doing this in C++ and in Java was that I had
to put an empty byte after each byte of the Java message.

Below is my Java code which produces the same MD5 hash as the C++ code
I posted earlier:

    public String getMD5Hash(String message, byte[] key) throws
IOException, NoSuchAlgorithmException
    {
        //set IBMJCE Provider
        Provider ibmJce = new IBMJCE();
        Security.insertProviderAt(ibmJce, 0);
       
        //create MD5 digest
        MessageDigest md = MessageDigest.getInstance("MD5");

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        java.security.DigestOutputStream dbaos = new
java.security.DigestOutputStream(baos, md);
       
        //put null bytes after each byte in the message
        //this is required to get the same result as the C++ application
        byte[] nulledMessage = new byte[message.getBytes().length * 2] ;   
        for (int i = 0; i < message.getBytes().length; i++)
        {
            nulledMessage[i*2] = message.getBytes()[i];
            nulledMessage[(i*2) + 1] = (byte)0;
        }
       
        //append the key to the beginning of the message
        byte[] concArray = new byte[nulledMessage.length + key.length];
        System.arraycopy (key, 0, concArray, 0, key.length);
        System.arraycopy (nulledMessage, 0, concArray, key.length,
nulledMessage.length);

        dbaos.write(concArray);
        dbaos.close();

        //get the MD5 digest
        byte[] digest = md.digest();
           
        //convert to hex string
        return toHexString(digest);
    }

I hope this helps the next person with this problem.

Happy Hashing,

Adrian
Roedy Green - 15 Jun 2004 06:48 GMT
>//put null bytes after each byte in the message
>        //this is required to get the same result as the C++ application
[quoted text clipped - 4 lines]
>            nulledMessage[(i*2) + 1] = (byte)0;
>        }

It would seem it might be easier to tackle this from the C++ end.  If
you feed it raw bytes with the same encoding, it should not be
inserting 0 bytes.  Perhaps the problem is avoiding accidental 0s
which are treated a string terminators in C++.

I don't know how you discovered this way to make them generate the
same value. Did you disassemble the C++ code?  The only part that was
obvious was that you wanted a plain MD5 digest, not a secret key MD5.

Signature

Canadian Mind Products, Roedy Green.
Coaching, problem solving, economical contract programming.
See http://mindprod.com/jgloss/jgloss.html for The Java Glossary.

Adrian - 16 Jun 2004 00:34 GMT
> >//put null bytes after each byte in the message
> >        //this is required to get the same result as the C++ application
[quoted text clipped - 13 lines]
> same value. Did you disassemble the C++ code?  The only part that was
> obvious was that you wanted a plain MD5 digest, not a secret key MD5.

I discovered this through lots and lots of trial and error.
Unfortunately I was not able to tackle this from the C++ end as I had
to conform to an existing (and untouchable) c++ application running on
a different system.
nobody - 16 Jun 2004 00:32 GMT
The reason the extra bytes are padded is that the CryptoAPI is encoding
the string using UTF-16LE (a 16-bit representation) rather than UTF-8.
So you need to do string.getBytes("UTF-16LE").  A much easier way to do
what you have below is:

public String getMD5Hash(String message, byte[] key)
        throws NoSuchAlgorithmException, UnsupportedEncodingException {
    MessageDigest digest = MessageDigest.getInstance("MD5");
    digest.update(key);
    return toHexString(digest.digest(message.getBytes("UTF-16LE")));
}

> 3) The difference between doing this in C++ and in Java was that I had
> to put an empty byte after each byte of the Java message.
[quoted text clipped - 46 lines]
>
> Adrian


Free Magazines

Get these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.